您当前的位置: 首页 >  Java

我的生物系学妹也能听懂的Java NIO中Write事件

发布时间:2021-05-29 16:14:41 ,浏览量:0

1 NIO Server端 1.1 多路复用开发一般步骤
//打开选择器 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(); } } 
2 写事件

对NIO的写操作:

  • 为何要注册写事件
  • 何时注册写事件
  • 为何写完后,还要取消注册写事件

若有channel在Selector上注册SelectionKey.OP_WRITE,在调用selector.select();时,系统会检查内核写缓冲区是否可写:

  • 可写,selector.select();立即返回,进入key.isWritable()
  • 何时不可写?比如缓冲区满,channel调用了shutdownOutPut等

除了注册写事件,也可在channel直接调用write(…),也可将数据发出去,但不灵活,可能浪费CPU。

比如服务端要发送一个200M Buffer,来看是否使用OP_WRITE事件的区别。

2.1 不使用事件

程序运行到这,会等200M文件发送完成后才继续往下执行,不符合异步事件模型。

若缓冲区一直处不可写状态,则该过程一直在这里死循环,浪费CPU。

// 200M Buffer ByteBuffer buffer = .... while(buffer.hasRemaining()) { // 该方法只会写入小于socket's output buffer空闲区域的任何字节数 // 并返回写入的字节数,可能是0字节 channel.write(buffer); } 
2.2 使用事件
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 空转,在写操作完成后,需将写事件取消,然后下次再有写操作时重新注册写事件。

2 NIO Client端 2.1 开发的一般步骤
// 打开选择器 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事件

起初对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,然后就能顺利进行channel读写。

若网络故障或远程主机故障,导致握手不成功,用户程序可通过finishConnect方法获得底层的异常通知,进而处理异常。

关注
打赏
1688896170
查看更多评论

暂无认证

  • 0浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.1044s