我们知道,系统运行的性能瓶颈通常在I/O读写,包括对端口和文件的操作上,过去,在打开一个I/O通道后,read()将一直等待在端口一边读取字节内容,如果没有内容进来,read()也是傻傻的等,这会影响我们程序继续做其他事情,那么改进做法就是开设线程,让线程去等待,但是这样做也是相当耗费资源的。

一、BIO带来的挑战

    BIO即阻塞I/O,不管是磁盘I/O还是网络I/O,数据在写入OutputStream或者从InputStream读取时都有可能会阻塞,一旦阻塞,线程将会失去CPU的使用权,这在当前的大规模访问量和有性能要求的情况下是不能被接收的。虽然当前的网络I/O有一些解决办法,如一个客户端一个处理线程,出现阻塞时只是一个线程阻塞而不会影响其他线程工作,还有为了减少系统线程的开销,采用线程池的办法来减少线程创建和回收的成本,但是有一些使用场景下仍然是无法解决的。如当前的一些需要大量HTTP长连接的情况。即使线程的数量不是问题,仍然有一些问题是无法避免的,比如我们想给某些客户端更高的服务优先级,很难通过设计线程的优先级来完成。

 

二、NIO的工作方式

  下图为NIO的相关类图:

其中有两个关键类:Channel和Selector,它们是NIO中的核心概念。

Channel:通道

Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流,而且他们面向缓冲区的。
正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在 UNIX 模型中,底层操作系统通道是双向的。

Selector:这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有事件发生时,他会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的socketchannel,然后,我们从这个Channel中读取数据,放心,包准能够读到,接着我们可以处理这些数据。

Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。

 

三、基于NIO的Socket请求的处理过程

 

 

四、代码示例

 

public void selector() throws IOException {ByteBuffer buffer = ByteBuffer.allocate(1024);Selector selector = Selector.open();ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);//设置为非阻塞方式ssc.socket().bind(new InetSocketAddress(8080));ssc.register(selector,SelectionKey.OP_ACCEPT);//注册监听事件while (true){Set selectedKeys = selector.selectedKeys();//取得所有KEY集合Iterator it = selectedKeys.iterator();while(it.hasNext()){SelectionKey key = (SelectionKey) it.next();if((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();SocketChannel sc = ssChannel.accept();//接收到服务端的请求sc.configureBlocking(false);sc.register(selector,SelectionKey.OP_READ);it.remove();}else if((key.readyOps() & SelectionKey.OP_READ)==SelectionKey.OP_READ){SocketChannel sc = (SocketChannel)key.channel();while(true){buffer.clear();int n = sc.read(buffer);//读取数据if(n<=0){break;}buffer.flip();}it.remove();}}}}