美文网首页
NIO的工作方式

NIO的工作方式

作者: 是一动不动的friend | 来源:发表于2017-12-11 21:11 被阅读39次

BIO带来的挑战:

BIO即阻塞I/O,不管是磁盘I/O还是网络I/O,数据在写入OutputStream或者从InputStream读取时都有可能会阻塞,一旦有阻塞,线程将会失去CPU的使用权,这在当前的大规模访问量和有性能要求的情况下是不能被接受的。虽然当前的网络I/O有一些解决办法,如一个客户端对应一个处理线程,出现阻塞时只是一个线程阻塞而不会影响其他线程工作,还有为了减少系统线程的开销,采用线程池的办法来减少线程创建和回收的成本,但是在一些使用场景下仍然是无法解决的。如当前一些需要大量HTTP长连接的情况,像淘宝现在使用的Web旺旺,服务端需要同时保持几百万的HTTP连接,但并不是每时每刻这些连接都在传输数据,在这种情况下不可能同时创建这么多线程来保持连接。如果要设置线程的优先级高低就更难了。

NIO的工作机制:

两个关键类Channel和Selector,它们是NIO中的两个核心概念。这里的Channel要比Socket更加具体,可以把它比作某种具体的交通工具,如汽车或高铁,而可把Selector比作一个车站的车辆运行调度系统,它将负责监控每辆车的当前运行状态。Selector可以轮询每个Channel的状态。这里还有一个Buffer类,Buffer类也比Stream类更加具体,它就相当于Selector车上的座位。

NIO引入了Channel、Buffer和Selector,就是想把Buffer上所装载的信息具体化,让程序员有机会控制它们。例如,当我们调用write()往SendQ中写数据时,当一次写的数据超过SendQ长度时需要按照SendQ的长度进行分割,在这个过程中需要将用户空间数据和内核地址空间进行切换,而这个切换不是你可控制的,但在Buffer中我们可以控制Buffer的容量、是否扩容以及如何扩容。

NIO是如何工作的:

调用Selector的静态工厂创建一个选择器,创建一个服务端的Channel,绑定到一个Socket对象,并把这个通信信道注册到选择器上,把这个通信信道设置为非阻塞模式。然后就可以调用Selector的selectedKeys方法来检查已经注册在这个选择器上的所有通信信道是否有需要的事件发生,如果有某个事件发生,将会返回所有的selectedKeys,通过这个对象的Channel方法就可以取得这个通信信道对象,从而读取通信的数据,而这里读取的数据是Buffer,这个Buffer是我们可以控制的缓冲器。

将Server端的监听连接请求的事件和处理请求的事件放在一个线程中,但是在事件应用中,我们通常会把它们放在两个线程中:一个线程专门负责监听客户端的连接请求,而且是以阻塞方式执行的;另外一个线程专门负责处理请求,这个专门处理请求的线程才会真正采用NIO的方式,像Web服务器Tomcat和Jetty都是使用这个处理方式。

如上图Selector可以同时监听一组通信信道上的I/O状态,前提是这个Selector已经注册到这些通信信道中。选择器Selector可以调用select()方法检查已经注册的通信信道上I/O是否已经准备好,如果没有至少一个信道I/O状态有变化,那么select方法阻塞等待或在超时事件后返回0。如果有多个信道有数据,那么将会把这些数据分配到对应的数据Buffer中。

NIO作出的改进就是“一个请求一个线程”,在连接到服务端的众多socket中,只有需要进行IO操作的才能获取服务端的处理线程进行IO。这样就不会因为线程不够用而限制了socket的接入。客户端的socket连接到服务端时,就会在事件分离器注册一个 IO请求事件 和 IO 事件处理器。在该连接发生IO请求时,IO事件处理器就会启动一个线程来处理这个IO请求,不断尝试获取系统的IO的使用权限,一旦成功(即:可以进行IO),则通知这个socket进行IO数据传输。

Buffer的工作方式:

可以把Buffer简单的理解为一组基本数据类型的元素列表,它通过几个变量来保存这个数据的当前位置状态如下:

capacity:缓冲区数组的总长度。

position下一个要操作的数据元素的位置。

limit:缓冲区数组中不可操作的下一个元素的位置,limit<=capacity

mark:用于记录当前position的前一个位置或者默认是0

创建一个11个字节的数组缓冲区,初始状态如上图。position的位置为0,capacity和limit默认都是数组长度

当我们写入5个字节后:

这时我们需要将缓冲区的5个字节数据写入Channel通信信道,所以我们调用byteBuffer.flip()方法,数组状态变化如下

处于这个状态时底层操作系统就可以读取缓冲区的数据发送出去了。

在下一次写数据之前我们再调一下clear()方法,缓冲区的索引状态又回到初始状态

AIO:

AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作;NIO则是发出IO请求后,由线程不断尝试获取IO权限,获取到后通知应用程序自己进行IO操作。

相关文章

网友评论

      本文标题:NIO的工作方式

      本文链接:https://www.haomeiwen.com/subject/axiuixtx.html