为什么要在java nio中的`selector.selectedKeys()。iterator()`中删除键?

我找到了一些java nio的示例代码:

ServerSocketChannel server = ServerSocketChannel.open(); Selector selector = Selector.open(); server.socket().bind(new InetSocketAddress(8080)); server.configureBlocking(false); server.register(selector, SelectionKey.OP_ACCEPT); while(true) { selector.select(); Iterator iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); // Why remove it? process(key); } } 

当他获得所选键时,他会删除循环中的键。 我们为什么要这样做?


UPDATE

感谢EJPuser270349提供的答案,我想我现在明白了,让我详细解释一下。

选择器中有2个表:

  1. 注册表:当我们调用channel.register ,会有一个新项(key)。 只有当我们调用key.cancel() ,它才会从此表中删除。

  2. 准备选择表:当我们调用selector.select() ,选择器将查找注册表,找到可用的键,将它们的引用复制到该选择表中。 选择器不会清除此表的项目(这意味着,即使我们再次调用selector.select() ,它也不会清除现有项目)

这就是为什么当我们从选择表中获取密钥时,我们必须调用iter.remove() 。 如果没有,我们将通过selector.selectedKeys()一次又一次地获取密钥,即使它还没有准备好使用。

因为Selector永远不会这样做,所以它只会添加到集合中,所以如果你不这样做,你将在下次Selector返回时自己重新处理事件。

因为在您执行此操作之前,您无法检测到新的事件重复。

因为从选定集中删除键,因为它已被处理,它将等待下一个选择事件。

选定集包含就绪通道的键。

  selector.select(); //This may block for a long time. Upon returning, the elected set contains keys of the ready channels. 

获取所选键集的迭代器并执行业务信息

 Iterator it = selector.selectedKeys().iterator( ); 

最后从选定的集合中删除密钥; 它被处理了

  it.remove( ); 

密钥可以直接从此集中删除,但不会添加。 尝试添加到选定的密钥集会抛出java.lang.UnsupportedOperationException。

您的问题更多是关于如何使用Iterator
https://www.tutorialspoint.com/java/java_using_iterator.htm

关于SelectorselectedKeys()方法返回的Set

[…]选定键组是一组键,这样每个键的通道都可以检测到为先前选择操作期间在键的兴趣集中识别的至少一个操作做好准备[…]

https://docs.oracle.com/javase/7/docs/api/java/nio/channels/Selector.html

您可以使用以下代码迭代“就绪”频道:

 while (true) { selectorRead.select(); for (SelectionKey key : selectorRead.selectedKeys()) { DatagramChannel dc = (DatagramChannel) key.channel(); } } 

如果你不调用删除方法,你会发现它也工作正常。

 import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; 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.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @SuppressWarnings({ "unchecked" }) public class NioServer { ServerSocketChannel serverChannel; ServerSocket serverSocket; public final int port; private Selector selector; ByteBuffer buffer = ByteBuffer.allocate(1024); NioServer(final int port) { this.port = port; } void init() throws Exception { // 创建 ServerSocketChannel、ServerSocket serverChannel = ServerSocketChannel.open(); serverSocket = serverChannel.socket(); serverSocket.bind(new InetSocketAddress(port)); // 设置通道为非阻塞模式serverChannel.configureBlocking(false); // 开启通道选择器,并注册 ServerSocketChannel selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } void go() throws Exception { while (true) { int num = selector.select(); if (num <= 0) continue; Iterator keyIter = selector.selectedKeys().iterator(); System.out.println(selector.selectedKeys().size()); //等于线程数量while (keyIter.hasNext()) { final SelectionKey key = keyIter.next(); System.out.println("所有的key"+key); // 接收一个Socket连接// key.isAcceptable()如果为true,说明channnel支持accept(),也就是说明是一个ServerSocketChannel if (key.isAcceptable()) { System.out.println("可以连接的key:"+key); SocketChannel clientChannel = serverChannel.accept(); if (clientChannel != null) { clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } // 如果isReadable()为true,说明是一个SocketChannel if (key.isReadable()) { String requestContent = read(key); // 业务处理// responseContent=doSomthing(requestContent); write(key, "ok" /* responseContent */); } // keyIter.remove(); } } } // 从通道读取数据String read(SelectionKey key) throws Exception { SocketChannel socketChannel = (SocketChannel) key.channel(); buffer.clear();// 这一步必须有int len = 0; StringBuffer str = new StringBuffer(); while ((len = socketChannel.read(buffer)) > 0) { byte[] bs = buffer.array(); String block = new String(bs, 0, len); System.out.println("Server read: " + block); str.append(block); } buffer.clear(); return str.toString(); } // 写数据到通道void write(SelectionKey key, String str) throws Exception { SocketChannel socketChannel = (SocketChannel) key.channel(); buffer.clear(); buffer.put(str.getBytes()); buffer.flip();// 这一步必须有socketChannel.write(buffer); } public static void main(String[] args) throws Exception { final int port = 10000; NioServer server = new NioServer(port); server.init(); /// ======================================================== // 接下来模拟3个Client并发访问服务器int poolsize = 1; ExecutorService pool = Executors.newFixedThreadPool(poolsize); Collection tasks = new ArrayList(10); final String clientname = "clientThread"; for (int i = 0; i < poolsize; i++) { final int n = i; // 若每一个Client都保持使用BIO方式发送数据到Server,并读取数据。 tasks.add(new Callable() { @Override public Object call() throws Exception { Socket socket = new Socket("127.0.0.1", port); final InputStream input = socket.getInputStream(); final OutputStream out = socket.getOutputStream(); final String clientname_n = clientname + "_" + n; // BIO读取数据线程new Thread(clientname_n + "_read") { @Override public void run() { byte[] bs = new byte[1024]; while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int len = 0; try { while ((len = input.read(bs)) != -1) { System.out.println("Clinet thread " + Thread.currentThread().getName() + " read: " + new String(bs, 0, len)); } } catch (IOException e) { e.printStackTrace(); } } } }.start(); // BIO写数据线程new Thread(clientname_n + "_write") { @Override public void run() { int a = 0; while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } String str = Thread.currentThread().getName() + " hello, " + a; try { out.write(str.getBytes()); a++; } catch (IOException e) { e.printStackTrace(); } } } }.start(); return null; } }); } pool.invokeAll((Collection>) tasks); server.go(); } } 

只需打印并测试它。你会发现即使你没有删除,使用key.isAcceptable()可以过滤相关的结果