3.NIO编程(重点)
与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel2种不同的套接字通道实现。这2种新增的通道都支持阻塞和非阻塞2种模式。
-
NIO类库简介
1、缓冲区Buffer:
(1)Buffer是一个对象,它包含一些要写入或者要读出的数据。而在面向流的IO中,可以将数据直接写入或读到Stream流对象中。在NIO库中,所有数据都是用缓冲区处理的。
(2)缓冲区实质上是一个数组,通常是一个字节数组。还提供了对数据的结构化访问以及维护读写位置等信息。
(3)最常用的缓存区是ByteBuffer。每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
(4)每一个Buffer类都是Buffer接口的一个子实例。除了ByteBuffer提供了一些特有的操作方便网络读写外,每一个Buffer类都有完全一样的操作,只是处理的数据类型不一样。
2、通道Channel:
(1)Channel是一个通道,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,而流只是在一个方向上移动。通道可以用于读写同时进行。
(2)因为Channel是全双工的,所以它可以比流更好地映射底层操作系统的API。因为特别是在UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作。
3、多路复用器Selector:
(1)Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的IO操作。
(2)一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll代替传统的select实现,所以它并没有最大连接句柄的限制。 -
NIO服务端序列图
1.打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道
2.绑定监听接口,设置连接为非阻塞模式。
3.创建Reactor线程,创建多路复用器并启动线程。
4.将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件。
5.多路复用器在线程run方法的无限循环体内轮询准备就绪的Key。
6.Selector监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路。
7.设置客户端链路为非阻塞模式。
8.将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的消息
9.异步读取客户端请求消息到缓冲区
10.对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中。
11.将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。
PS:如果发送区TCP缓冲区满,会导致写半包。此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓存区。 -
NIO客户端序列图
1.打开SocketChannel,绑定客户端本地地址(可选,默认会随机分配一个可用的本地地址)。
2.设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数。
3.异步连接服务端。
4.判断是否连接成功,如果成功,则直接注册读状态位到Selector中;如果当前没有连接成功(异步连接,返回false,说明客户端已经发送sync包,服务端没有返回ack包,物理链路还没有建立)
5.向Reactor线程的Selector注册OP_CONNECT状态位,监听服务端的TCPACK应答。
6.创建Reactor线程,创建多路复用器并启动线程。
7.多路复用器在线程run方法的无限循环体内轮询准备就绪的Key。
8.接收connect事件进行处理。
9.判断连接结果,如果连接成功,注册读事件到多路复用器。
10.注册读事件到多路复用器。
11.异步读客户端请求消息到缓存区。
12.对ByteBuffer进行编解码,如果有半包消息接收缓冲区Reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中。
13.将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。 -
NIO编程的优点:
(1)客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞。
(2)SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回。
(3)线程模型的优化:它没有连接句柄数的限制且性能不会随着客户端的增加而线性下降。