Java NIO简单示例

BIO:阻塞IO,阻塞Socket的accept方法,直到建立连接

NIO:非阻塞IO,所有连接注册到Selector上作为一个事件,服务端通过轮询这个Selector来查看是否需要建立连接或执行某些 感兴趣的事件

NIO服务端:

public class NioServer {
    public static void main(String[] args) {
        Selector selector = null;
        ServerSocketChannel serverSocketChannel = null;
        int port = 8999;

        try {
        	// 为服务端开启Selector以及服务端通道ServerSocketChannel
            selector = Selector.open();
            // 服务端通道代表所有可能注册到当前Selector中的连接请求
            serverSocketChannel = ServerSocketChannel.open();

            serverSocketChannel.bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            // 使用 ServerSocketChannel 在当前Selector上注册ACCEPT事件
            // 即只关心 ServerSocketChannel 是否能建立连接
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

			// 创建线程池用于建立连接后,每个连接的读写操作处理
            ExecutorService executor = Executors.newFixedThreadPool(10);

            System.err.println("服务端初始化完成,监听端口:" + port);

			// 开启服务端 Selector 轮询
            while (true) {
            	// 服务端 Selector 上没有感兴趣的事件(此处是ACCEPT事件)则 continue;
                if (selector.select() <= 0) {
                    continue;
                }
                // 直到 Selector 上注册了感兴趣的事件(此处是ACCEPT事件)
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                // 获取到每一个连接,交由线程池处理,传递连接的KEY
                while (iterator.hasNext()) {
                    executor.submit(new NioServerHandler(iterator.next()));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

每个连接都会交由线程池去管理,所以接下来看看线程池中管理的线程在做什么;

NioServerHandler.java

public class NioServerHandler implements Runnable{

	// 服务端传递的连接KEY
    private SelectionKey key;
    // 注意:此处的Selector不是上面服务端的Selector,而是针对于每个连接自身的Selector
    // 当连接建立后(ACCEPT完成),用于 客户端连接 的读写事件注册,并轮询这个 Selector 处理
    private Selector selector;

    public NioServerHandler(SelectionKey key) {
        this.key = key;
    }
    
    private void doAccept() throws IOException {
    	// 新连接进来,当前类的 Selector 属性为 null
        if (selector == null) {
        	// 打开当前连接专属的 Selector
            selector = Selector.open();
            // doAccept
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            // 建立连接,获取客户端通道 SocketChannel
            SocketChannel accept = serverSocketChannel.accept();
            accept.configureBlocking(false);

			// 为客户端连接注册读事件(READ)
            accept.register(selector, SelectionKey.OP_READ);
            // 移除掉 服务端Selector 中传递的当前的KEY,因为已经建立了连接
            key.selector().selectedKeys().remove(key);

            System.err.println("Server Accepted");
        }
    }

    @Override
    public void run() {
        try {
        	// 处理Accept事件,与客户端建立连接
            doAccept();
            
            // 开启客户端连接 Selector 轮询
            while (true) {
            	// 没有感兴趣的事件则 continue;
                if (selector.select() <= 0) {
                    continue;
                }
                // 获取到感兴趣的事件,如 READ
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 这一步是防止客户端断开连接后,没有正确关闭Socket导致服务端报错
                    this.key = selectionKey;
                    
                    // 可读
                    if (selectionKey.isReadable()) {
                    	// 获取到客户端 SocketChannel, 执行读操作
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        while (channel.read(byteBuffer) > 0) {
                            System.out.println(new String(byteBuffer.array()));
                            byteBuffer.flip();
                        }
                        // 读完后去除当前KEY,并重新注册写事件
                        selector.selectedKeys().remove(selectionKey);
                        channel.register(selector, SelectionKey.OP_WRITE);
                        
                    } else if (selectionKey.isWritable()) { // 可写
                    	
                    	// 获取客户端连接通道 SocketChannel
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        // 执行写操作,而后去除当前KEY
                        channel.write(ByteBuffer.wrap("Hey Client".getBytes()));
                        selector.selectedKeys().remove(selectionKey);

						// 这里默认写操作后释放客户端连接,当然也可以重新注册别的事件
                        selectionKey.cancel();
                        channel.socket().close();
                        channel.close();
                    }
                }
            }
        } catch (IOException e) {
        	// 由于key值在每次循环都有赋值,所以当客户端断开连接后,捕获到异常后正确关闭Socket
            key.cancel();
            System.err.println("与客户端断开连接");
        }
    }
}

客户端也同样使用NIO的方式处理事件:
NioClient.java

public class NioClient {
    public static void main(String[] args) throws IOException {
    	// 客户端也可以开启Selector,通过建立连接注册自己感兴趣的事件
        Selector selector = Selector.open();
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        // 注册CONNECT事件,当连接建立将触发此事件
        channel.register(selector, SelectionKey.OP_CONNECT);
        // 建立与服务端连接
        channel.connect(new InetSocketAddress(8999));

		// 开启轮询
        while (true) {
        	// 当连接正确建立并有感兴趣的事件发生
            if (selector.select() > 0 && channel.finishConnect()) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
         	        // 获取KEY
                    SelectionKey key = iterator.next();
                    // 触发连接事件
                    if (key.isConnectable()) {
                    	// 获取与服务端连接通道
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        socketChannel.configureBlocking(false);
                        // 连接后向服务端发送数据
                        socketChannel.write(ByteBuffer.wrap("From NioClient...".getBytes()));
                        // 重新注册 READ事件到Selector,并移除原有的KEY
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        selector.selectedKeys().remove(key);
                    } else if (key.isReadable()) {
                    	// 当服务端响应后触发此处的 READ事件
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        while (socketChannel.read(byteBuffer) > 0) {
                            System.out.println("收到服务端响应:" + new String(byteBuffer.array()));
                            byteBuffer.flip();
                        }
                        // 获得响应则释放连接,请求结束
                        key.cancel();
                        socketChannel.socket().close();
                        socketChannel.close();
                        return;
                    }
                }
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值