本文共 3569 字,大约阅读时间需要 11 分钟。
@author:Jingdai
@date:2021.06.27今天用 NIO 时出现了一个Bug,搞了半天,现在记录一下。
使用 NIO 进行通信时,在客户端正常关闭时(即调用close() 方法关闭),会触发服务器的一个读事件,此时服务器的 read 方法会返回 -1,根据此我们就可以做一些处理,如下代码。
int n = socketChannel.read(byteBuffer);if (n == -1) { selectionKey.cancel();}
但是今天写代码发现客户端正常关闭后服务器端总是读不到 -1,无法取消这个 key,导致服务器端无法阻塞,一直空转。示例代码如下。
Server.java
import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;import java.util.Iterator;public class Server { public static void main(String[] args) { int port = 8888; try { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); ssc.bind(new InetSocketAddress(port)); Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Iteratoriterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if (selectionKey.isAcceptable()) { SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { SocketChannel sc = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer; if (selectionKey.attachment() == null) { byteBuffer = ByteBuffer.allocate(512); selectionKey.attach(byteBuffer); } else { byteBuffer = (ByteBuffer) selectionKey.attachment(); } int n = sc.read(byteBuffer); System.out.println(n); if (n == -1) { selectionKey.cancel(); } else { byteBuffer.flip(); System.out.println(Charset.defaultCharset().decode(byteBuffer)); // byteBuffer.clear(); } } iterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } }}
Client.java
import java.io.IOException;import java.io.OutputStream;import java.net.Socket;public class Client { public static void main(String[] args) { String ip = "localhost"; int port = 8888; try (Socket socket = new Socket(ip, port)) { OutputStream os = socket.getOutputStream(); os.write("Hello Server!".getBytes()); os.flush(); } catch (IOException e) { e.printStackTrace(); } }}
最后发现在客户端正常关闭时,如果 read(ByteBuffer dst) 方法的 dst 里面没有数据,则 read() 方法就会正常的返回 -1;而如果 dst 里面已经有了数据的话,那么 read() 方法就会返回 0 。所以上面代码中就无法进入到 n == -1 的那个分支,即无法取消这个 key,这就导致 select 无法阻塞,一直空转。知道了原因,也就很好解决了,只要将上面代码中那行注释的代码去掉注释就行,读取完之后 clear 会重置ByteBuffer ,就可以使 read 方法返回 -1了,当然每次读取都使用一个新的 ByteBuffer 也是可以的。
当客户端正常关闭时,会触发服务器的读事件。如果服务器端的 read(ByteBuffer dst) 方法的 dst 里面没有数据,则 read() 方法就会正常的返回 -1;而如果 dst 里面已经有了数据的话,那么 read() 方法就会返回 0 。
转载地址:http://gomen.baihongyu.com/