使用NIO实现非阻塞式的网络通信

3 篇文章 0 订阅
1 篇文章 0 订阅

  实现这样一个程序:客户端读取键盘输入,并发送到服务器端,服务器端接收信息并打印。
  首先先写一个阻塞式的程序:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.Scanner;

import org.junit.Test;

public class TestBlocking {

    @Test
    public void client() throws IOException {

        // 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        // 分配1024字节大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);

        // 输入
        while (scan.hasNext()) {

            String str = scan.next();

            buffer.put((new Date().toString() + "\n" + str).getBytes());

            // 切换回读模式,实质是令limit=position;position=0。
            buffer.flip();
            // 写入通道
            socketChannel.write(buffer);
            // 清空缓冲区
            buffer.clear();
        }
    }

    @Test
    public void server() throws IOException {
        // 获取服务器端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));

        // 接收客户端通道
        SocketChannel socketChannel = serverSocketChannel.accept();

        // 分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 每次读取数据的长度
        int len;

        // 接收数据
        while ((len = socketChannel.read(buffer)) > 0) {

            buffer.flip();

            // 将每次读取的数据输出
            System.out.println(new String(buffer.array(), 0, len));

            buffer.clear();
        }
    }
}

  运行程序(注意先启动server,再启动client),发现当只有一个server一个client时可以正常运行:
  
  image_1b8of1b9e15vmro7mu44e1uc113.png-22.9kB
  
  但是再运行一遍client以添加一个client线程,会发现第二次添加的client线程发送的信息无法显示,这是因为,服务器端的线程一直阻塞在接收第一个客户端线程发送信息的位置,即63行,无法接收第二个客户端线程的连接请求。
  
  现在,我们使用NIO来实现非阻塞式的程序,以解决上述问题:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

public class TestNonBlocking {

    @Test
    public void client() throws IOException {

        // 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        // 分配1024字节大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);

        // 输入
        while (scan.hasNext()) {

            String str = scan.next();

            buffer.put((new Date().toString() + "--" + str).getBytes());

            // 切换回读模式,实质是令limit=position;position=0。
            buffer.flip();
            // 写入通道
            socketChannel.write(buffer);
            // 清空缓冲区
            buffer.clear();
        }
    }

    @Test
    public void server() throws IOException {
        // 获取服务器端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 切换该通道为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));

        // 获取选择器
        Selector selector = Selector.open();

        // 将通道注册到选择器上,并指定监听的事件类型为“接收”
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 轮询式地获取选择器上已经准备就绪的事件
        //selector.select()方法是阻塞的
        while (selector.select() > 0) {

            // 获取当前选择器中所有已经注册的“选择键”,即已就绪的监听事件
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()) {
                // 获取已经准备就绪的事件
                SelectionKey selectionKey = it.next();

                // 判断事件的类型
                // 接收类型
                if (selectionKey.isAcceptable()) {
                    // 接收客户端通道
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    socketChannel.configureBlocking(false);

                    socketChannel.register(selector, selectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    // 获取当前选择器上“读就绪”状态的通道
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    int len;

                    while ((len = socketChannel.read(buffer)) > 0) {
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, len));
                        buffer.clear();
                    }
                }
                // 取消选择键 SelectionKey
                it.remove();
            }

        }
    }
}

  再次运行程序,发现即使在多个client线程的情况下,程序也能正常运行:
  
  image_1b8of8ec5un4hrg1eis15csqlr1g.png-25.2kB

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值