非阻塞式IO通信
一、简介
非阻塞IO(NIO)弥补了原来同步阻塞IO的不足,NIO有三个重要概念:
缓冲区Buffer:缓冲待读写处理的数据,NIO是读写数据操作的就是Buffer;
通道Channel:数据通过的双向通道;
多路复用器Selector:负责多路复用;
二、NIO服务端&客户端流程
下面是NIO服务端的时序图:

打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道(对应BIO的ServerSocket);
绑定监听端口,设置连接方式为非阻塞模式;
创建Reactor线程,创建多路复用器Selector并启动线程;
将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件;
多路复用器在线程run方法的无限循环体内轮询准备就绪的Key;
多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路;
设置客户端链路为非阻塞模型;
将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的网络消息;
异步读取客户端消息到缓冲区;
对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排;
将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。
注意:如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓冲区。
下面是NIO客户端的时序图:

打开SocketChannel,绑定客户端本地地址(可选,默认系统会随机分配一个可用的本地地址);
设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数;
异步连接服务端;
判断是否连接成功,如果连接成功则直接注册读状态位到多路复用器中(步骤10),如果当前没有连接成功(异步连接,返回false,说明客户端已经发送sync包,服务端没有返回ack包,物理链路还没有建立),向多路复用器注册连接状态位(步骤5);
向Reactor线程的多路复用器注册OP_CONNECT状态位,监听服务端的TCP ACK应答;
创建Reactor线程,创建多路复用器并启动线程;
多路复用器在线程run方法的无限循环体内轮询准备就绪的key;
接收connect事件并进行处理;
判断连接是否完成,如果完成执行步骤10;
注册读事件到多路复用器;
异步读客户端请求到缓冲区;
对ByteBuffer进行编解码,如果有半包消息接收缓冲区Reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排。
将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。
三、代码实例
3.1 服务端
TimeServer.java
MultiplexerTimeServer.java
3.2 客户端
TimeClient.java
TimeClientHandle.java
3.3 运行服务端和客户端
服务端运行结果:
客户端运行结果:
四、NIO的好处
客户端发起的链接操作是异步的,可以通过多路复用器注册OP_CONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞;
SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样IO通信线程就可以处理其他的链路,不需要同步等待这个链路可用;
线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制(只受限于操作系统的最大句柄数或者单个进程的句柄数限制),这意味着一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而性能下降。因此,它非常适合做高性能、高负载的网络服务器。
参考:
《Netty权威指南》
Last updated
Was this helpful?