NIO Server端多路复用开发的通常步骤是:
//打开选择器 Selector selector = Selector.open(); //打开通到 ServerSocketChannel socketChannel = ServerSocketChannel.open(); //配置非阻塞模型 socketChannel.configureBlocking(false); //绑定端口 socketChannel.bind(new InetSocketAddress(8080)); //注册事件,OP_ACCEPT只适用于ServerSocketChannel socketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectionKeys.iterator(); while(iter.hasNext()) { SelectionKey key = iter.next(); if(key.isAcceptable()) { SocketChannel channel = ((ServerSocketChannel)key.channel()).accept(); channel.configureBlocking(false); channel.register(selector,SelectionKey.OP_READ); } if(key.isWritable()) { } if(key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer readBuffer = ByteBuffer.allocate(512); channel.read(readBuffer); readBuffer.flip(); //handler Buffer //通常是响应客户端的数据 //直接是write写不就完事了嘛,为啥须要write事件? //channel.write(...) } iter.remove(); } }
刚开始对NIO的写操做理解的不深,不知道为何要注册写事件,什么时候注册写事件,为何写完以后要取消注册写事件。java
若是有channel
在Selector
上注册了SelectionKey.OP_WRITE
事件,在调用selector.select();
时,系统会检查内核写缓冲区是否可写(何时是不可写的呢,好比缓冲区已满,channel
调用了shutdownOutPut
等等),若是可写,selector.select();
当即返回,随后进入key.isWritable()
分支。编程
固然你在channel
上能够直接调用write(...)
,也能够将数据发送出去,但这样不够灵活,并且可能浪费CPU
。网络
看一个场景,服务端须要发送一个200M
的Buffer
,看看使用OP_WRITE
事件和不使用的区别。异步
//不使用事件,缺点是,程序运行到这会等到200M文件发送完成后才继续往下执行,不符合异步事件模型 //的编程思想,若是缓冲区一直处于不可写状态,那么该过程一直在这里死循环,浪费了CPU资源。 ByteBuffer buffer = .... //200M的Buffer while(buffer.hasRemaining()) { //该方法只会写入小于socket's output buffer空闲区域的任何字节数 //并返回写入的字节数,多是0字节。 channel.write(buffer); } //使用事件的方式,谁好谁坏,一看便知 if(key.isReadable()) { ByteBuffer buffer = .... //200M的Buffer //注册写事件 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); //绑定Buffer key.attach(buffer); } //isWritable分支 if(key.isWritable()) { ByteBuffer buffer = (ByteBuffer) key.attachment(); SocketChannel channel = (SocketChannel) key.channel(); if (buffer.hasRemaining()) { channel.write(buffer) } else { //发送完了就取消写事件,不然下次还会进入该分支 key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); } }
客户端开发的通常步骤:
//打开选择器 Selector selector = Selector.open(); //打开通道 SocketChannel socketChannel = SocketChannel.open(); //配置非阻塞模型 socketChannel.configureBlocking(false); //链接远程主机 socketChannel.connect(new InetSocketAddress("127.0.0.1",8080)); //注册事件 socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); //循环处理 while (true) { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iter = keys.iterator(); while(iter.hasNext()) { SelectionKey key = iter.next(); if(key.isConnectable()) { //链接创建或者链接创建不成功 SocketChannel channel = (SocketChannel) key.channel(); //完成链接的创建 if(channel.finishConnect()) { } } if(key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(500 * 1024 * 1024); buffer.clear(); channel.read(buffer); //buffer Handler } iter.remove(); } }
起初对OP_CONNECT
事件还有finishConnect
不理解,OP_CONNECT
事件什么时候触发,特别是为何要在key.isConnectable()
分支里调用finishConnect
方法后才能进行读写操做。socket
首先,在non-blocking
模式下调用socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
链接远程主机,若是链接能当即创建就像本地链接同样,该方法会当即返回true
,不然该方法会当即返回false
,而后系统底层进行三次握手创建链接。链接有两种结果,一种是成功链接,第二种是异常,可是connect
方法已经返回,没法经过该方法的返回值或者是异常来通知用户程序创建链接的状况,因此由OP_CONNECT
事件和finishConnect
方法来通知用户程序。无论系统底层三次链接是否成功,selector
都会被唤醒继而触发OP_CONNECT
事件,若是握手成功,而且该链接未被其余线程关闭,finishConnect
会返回true
,而后就能够顺利的进行channle
读写。若是网络故障,或者远程主机故障,握手不成功,用户程序能够经过finishConnect
方法得到底层的异常通知,进而处理异常。线程