在网络通信中,传统的BIO即ServerSocket --socket模型中,一个客户端的连接就会占用一个线程,此线程会一直等待IO操作的发生,直到被完成。所以N个客户端就会有N个线程与之对应。弊端就是在大量连接,高并发的情况下就会导致线程资源的浪费以及性能的低下。
NIO,即non-blocking IO,非阻塞式IO。区别于传统的原生的BIO(阻塞式IO),在NIO模型中,一个线程可以处理多个客户端连接,不会导致线程的阻塞和资源的浪费。
NIO 三大核心:
Buffer缓冲区:本质是一个可读写的内存块,在这个内存块维护了一个hb数组,数据实际是存入这个数组的。对于不同类型的数据有对应的Buffer数组,如IntBuffer,ByteBuffer......。Buffer提供了一系列方法去操作这个数组。支持类型化的操作。
Buffer父类对所有缓冲区都提供了四个标记性的属性来进行访问的控制:mark,limit,capacity,position。访问前先调用一个flip,clear方法来对这些属性进行重置反转。flip表示从写切换到读(limit=position,position=0)。clear则是切换到写(position=0,limit=capacity,对原数据进行覆盖的操作)。
总的对应关系就是一个线程对应一个Selector,一个Selector注册多个Channel,一个Channel对应一个Buffer。Selector选择哪个Channel去处理是由事件决定的。
Channel通道:类似于BIO中的流,流的特点就像流的分类名一样,要么读要么写,是单向传输的。而通道(接口)是基于Buffer进行操作的,可读可写,是双向传输。
通道和流是一种被包含的关系,可以通过原始的流获取通道。基于通道进行数据的读写(流只能读或写)。针对写操作,是从Buffer往通道写,读则反过来。
当然两个通道可以操作同一个Buffer,例如将通道A的数据复制到通道B,可以通过while实现,也可以直接通过一个封装好的方法实现数据的迁移- Channel.transferTo(destChannel,size)。
Selector选择器:一个线程对应一个Selector,一个Selector上可以注册多个通道,选择器通过监听通道上的事件(如读事件,写事件,连接事件等待)发生,选择对应通道处理对应的事件,如果监听的通道没有可读的数据,不会阻塞,继续监听其他通道。
客户端连接时,通过ServerSocketChannel得到SocketChannel。这个socketchannel代表了具体的读写通道,将它注册到选择器会返回一个与之关联的selectionKey。通过选择器的监听,如果有事件发生了,会得到一个selectionKey的集合,代表了发生事件的通道。通过这个key可以反向获取对应的socketChannel,从而处理对应的通道事件。
服务端
客户端: