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

Java NIO中Readable SelectionKey反复出现在SelectedKeys的问题

Java NIO TCP服务器重复触发可读SelectionKey问题解决

问题根源分析

你的代码里有三个关键问题导致了重复触发可读事件:

1. 客户端数据发送逻辑错误

客户端代码中,byteBuffer.flip()后立刻调用了byteBuffer.clear(),这会重置缓冲区的position为0、limit为容量,导致后续socketChannel.write(byteBuffer)写入的是缓冲区里的无效空数据,并且客户端直接关闭了连接。

2. 服务器未处理连接关闭场景

当客户端关闭连接后,服务器端的SocketChannel.read()会返回-1,这表示TCP流已经到达末尾。此时如果不取消该SelectionKey并关闭通道,Selector会持续将这个Key标记为可读(EOF属于可读事件的触发条件),导致每次select()都会把它加入selectedKeys集合。

3. 错误注册了OP_CONNECT事件

服务器通过accept()获取的SocketChannel已经处于连接完成状态,OP_CONNECT事件是给客户端SocketChannel使用的(用于监听连接是否建立),服务器端注册这个事件完全多余,还可能干扰Selector的事件判断。

修复方案

修复客户端代码

移除byteBuffer.clear()调用,确保有效数据能正确写入通道:

public class TcpClient {
    public static void main(String[] args) {
        SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 885);
        SocketChannel socketChannel = null;
        try {
            socketChannel = SocketChannel.open(socketAddress);
            socketChannel.configureBlocking(false);
            while (!socketChannel.finishConnect()) {
            }
            ByteBuffer byteBuffer = ByteBuffer.allocate(240);
            byteBuffer.put("Hello Kurumi".getBytes());
            byteBuffer.flip();
            // 移除错误的clear()调用
            socketChannel.write(byteBuffer);
            socketChannel.shutdownOutput();
            socketChannel.close();
        } catch (IOException e) {
            System.out.println(e);
            throw new RuntimeException(e);
        }
    }
}

修复服务器代码

  1. 移除不必要的OP_CONNECT事件,注册SocketChannel时只关注OP_READ:
// 替换原注册代码
socketChannel.register(selector, SelectionKey.OP_READ);
  1. 新增连接关闭处理逻辑,当read()返回-1时取消SelectionKey并关闭通道:
else if (selectionKey.isReadable()) {
    System.out.println("is readable");
    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(24);
    int readBytes;
    while ((readBytes = socketChannel.read(byteBuffer)) > 0) {
        byteBuffer.flip();
        System.out.println(byteBuffer.limit());
        System.out.println(StandardCharsets.UTF_8.decode(byteBuffer).toString());
        byteBuffer.clear();
    }
    // 处理客户端关闭连接的情况
    if (readBytes == -1) {
        System.out.println("客户端已关闭连接");
        selectionKey.cancel(); // 取消Key的注册
        socketChannel.close(); // 关闭通道
        continue;
    }
}

额外说明

你之前从selectedKeys迭代器中移除Key,只是把它从已选择集合中清除,但并没有取消该Key在Selector上的注册状态。只要Key还在注册,并且对应的事件触发条件(比如这里的EOF)持续满足,下次select()就会再次把它加入selectedKeys集合。只有调用selectionKey.cancel()才能彻底把Key从Selector中移除,避免后续事件触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 10:29:54