我们知道,系统运行的性能瓶颈通常在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:通道
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();}}}}