You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Selector.select()读取SelectionKey.isReadable后不再阻塞重复读消息问题

问题原因与修复方案

核心问题分析

你的代码存在两个关键问题,导致selectorIO.select()无法阻塞并重复读取旧消息:

  1. ByteBuffer未在每次循环前重置:
    仅在方法开头调用了一次buffer.clear(),进入循环后未重新清空缓冲区。第一次读取完成后,缓冲区的位置和限制未重置,后续读取操作会重复使用缓冲区里的旧数据,即便没有新消息也会输出之前的内容。

  2. 未处理连接关闭与读取返回值的情况:
    客户端发送完消息后,若未关闭连接,channel.read(buffer)会返回0(无新数据);若客户端关闭连接,read会返回-1。这两种情况都未处理,导致SelectionKey一直处于可读状态,selector.select()会立即返回,触发重复的读事件。

修复后的代码

public void readData() {
    ByteBuffer buffer = ByteBuffer.allocate(8);
    while(true) {
        try {
            int numberOfKeys = selectorIO.select();
            if (numberOfKeys == 0) {
                continue;
            }

            Set<SelectionKey> keys = selectorIO.selectedKeys();
            Iterator<SelectionKey> itr = keys.iterator();
            
            while(itr.hasNext()) {
                SelectionKey key = itr.next();
                itr.remove();
                
                if(key.isReadable()) {
                    SocketChannel channel = (SocketChannel)key.channel();
                    buffer.clear();
                    int numread = channel.read(buffer);
                    
                    if (numread == -1) {
                        key.cancel();
                        channel.close();
                        continue;
                    }
                    
                    if (numread == 0) {
                        continue;
                    }
                    
                    buffer.flip();
                    String s = new String(buffer.array(), 0, numread);
                    System.out.println(s);
                    System.out.println(numread);
                    
                    buffer.flip();
                    int numwrote = channel.write(buffer);
                    System.out.println(numwrote + " Bytes written");
                }   
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

关键修复点说明

  • 每次读取前清空缓冲区:调用channel.read(buffer)前执行buffer.clear(),确保缓冲区处于可写入状态,避免旧数据干扰。
  • 处理read返回值:
    • 当numread == -1时,说明客户端已关闭连接,取消SelectionKey并关闭通道,避免无效读事件触发。
    • 当numread == 0时,说明当前无新数据可读,直接跳过后续处理,让selector重新进入阻塞状态。
  • 正确构造字符串:使用new String(buffer.array(), 0, numread)仅转换实际读取到的字节,避免缓冲区未填充部分的乱码。
  • 提前移除SelectionKey:处理key前调用itr.remove(),符合NIO规范,避免同一个key被重复处理。

内容的提问来源于stack exchange,提问作者Matthew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 23:50:19