Java NIO学习笔记(三) 使用Selector客户端与服务器的通信

本文介绍了Java NIO中Selector的使用,通过非阻塞方式提升服务器处理客户端连接的效率。Selector不断轮询注册的Channel,检测连接、读写事件,避免了传统IO的线程-per-connection模式。文章详细阐述了NIO服务器的创建过程,包括ServerSocketChannel的配置、Selector的注册与轮询、客户端连接的处理等步骤,展示了NIO在高并发场景下的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用NIO的一个最大优势就是客户端于服务器自己的不再是阻塞式的,也就意味着服务器无需通过为每个客户端的链接而开启一个线程。而是通过一个叫Selector的轮循器来不断的检测那个Channel有消息处理。
简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的Set集合,进行后续的I/O操作。
由于select操作只管对selectedKeys的集合进行添加而不负责移除,所以当某个消息被处理后我们需要从该集合里去掉。

一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll()代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这确实是个非常巨大的进步。

下面,我们通过NIO编程的序列图和源码分析来熟悉相关的概念,以便巩固我们前面所学的NIO基础知识。

这里写图片描述

下面,我们对NIO服务端的主要创建过程进行讲解和说明,作为NIO的基础入门,我们将忽略掉一些在生产环境中部署所需要的一些特性和功能(比如TCP半包等问题)。

步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道,代码示例如下。

ServerSocketChannel server = ServerSocketChannel.open();

步骤二:绑定监听端口,设置连接为非阻塞模式,示例代码如下。

server.socket().bind(new InetSocketAddress(7777),1024);
        // 设置为非阻塞模式, 这个非常重要
        server.configureBlocking(false);

步骤三:创建Reactor线程,创建多路复用器并启动线程,代码如下。

        Selector selector = Selector.open();
        new Thread(new ReactorTask()).start();

步骤四:将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件,代码如下。

server.register(selector, SelectionKey.OP_ACCEPT);

步骤五:多路复用器在线程run方法的无限循环体内轮询准备就绪的Key,代码如下。

while(true){
            selector.select(1000);
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            SelectionKey key = null;
            while (it.hasNext()) {
                key = (SelectKey)it.next();
               //处理io
            }
}

步骤六:多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路,代码示例如下。

// 得到与客户端的套接字通道
SocketChannel channel = ssc.accept();

步骤七:设置客户端链路为非阻塞模式,示例代码如下。

channel.configureBlocking(false);

步骤八:将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,用来读取客户端发送的网络消息,代码如下。

 channel.register(selector, SelectionKey.OP_READ);

步骤九:异步读取客户端请求消息到缓冲区,示例代码如下。

int readBytes = channel.read(byteBuffer);

步骤十:对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排,示例代码如下。

Object message = null;
while (buffer.hasRemain()) {
    buffer.mark();
    message = decode(buffer);
    if(message == 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值