这期内容当中小编将会给大家带来有关怎么在Java中使用NIO的Write事件,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
//打开选择器 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(1024); channel.read(readBuffer); readBuffer.flip(); // handler Buffer // 一般是响应客户端的数据 // 直接是write写不就完事了嘛,为啥需要write事件? // channel.write(...) } iter.remove(); } }
对NIO的写操作:
为什么要注册写事件
何时注册写事件
为什么写完之后要取消注册写事件
如果有channel在Selector上注册了SelectionKey.OP_WRITE
,在调用selector.select();时,系统会检查内核写缓冲区是否可写:
如果可写,selector.select();
立即返回,进入key.isWritable()
何时不可写?比如缓冲区已满,channel调用了shutdownOutPut等
当然除了注册写事件,你也可以在channel直接调用write(…),也可以将数据发出去,但这样不够灵活,而且可能浪费CPU。
比如服务端需要发送一个200M的Buffer,看看是否使用OP_WRITE事件的区别。
程序运行到这会等到200M文件发送完成后才继续往下执行,不符合异步事件模型的思想。若缓冲区一直处不可写状态,则该过程一直在这里死循环,浪费CPU。
// 200M的Buffer ByteBuffer buffer = .... while(buffer.hasRemaining()) { // 该方法只会写入小于socket's output buffer空闲区域的任何字节数 // 并返回写入的字节数,可能是0字节 channel.write(buffer); }
if(key.isReadable()) { // 200M Buffer ByteBuffer buffer = .... // 注册写事件 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); // 绑定Buffer key.attach(buffer); } // 可写分支 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 注册该通道的写事件,跟注册读事件一样,当底层写缓冲区有空闲就会触发写事件了,而一般来说底层的写缓冲区大部分都是空闲的。所以一般只要注册了写事件,就会立马触发了,为了避免 cpu 空转,在写操作完成后需要把写事件取消掉,然后下次再有写操作时重新注册写事件。
开发的一般步骤
// 打开选择器 Selector selector = Selector.open(); // 打开通道 SocketChannel socketChannel = SocketChannel.open(); // 配置非阻塞模型 socketChannel.configureBlocking(false); // 连接Server 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方法后才能进行读写操作。
首先,在non-blocking模式下调用socketChannel.connect(new InetSocketAddress(“127.0.0.1”,8080));连接远程主机,如果连接能立即建立就像本地连接一样,该方法会立即返回true,否则该方法会立即返回false,然后系统底层进行三次握手建立连接。连接有两种结果,一种是成功连接,第二种是异常,但是connect方法已经返回,无法通过该方法的返回值或者是异常来通知用户程序建立连接的情况,所以由OP_CONNECT事件和finishConnect方法来通知用户程序。不管系统底层三次连接是否成功,selector都会被唤醒继而触发OP_CONNECT事件,如果握手成功,并且该连接未被其他线程关闭,finishConnect会返回true,然后就可以顺利的进行channle读写。如果网络故障,或者远程主机故障,握手不成功,用户程序可以通过finishConnect方法获得底层的异常通知,进而处理异常。
上述就是小编为大家分享的怎么在Java中使用NIO的Write事件了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。