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

Java NIO非阻塞读写服务器二次运行客户端时卡壳问题排查

NIO服务器重复运行客户端卡顿问题排查与修复

核心问题分析及修复方案

1. SelectionKey被无条件取消,导致后续通道事件失效

在isWritable分支中,代码无论当前状态是否完成处理,都会执行key.cancel(),直接将通道的监听Key从Selector中移除。首次客户端处理后Key被取消,再次连接新客户端时,其通道的Key会在触发可写事件后被直接取消,导致后续无法处理该通道的任何事件,出现卡顿。

修复:仅在完成Write逻辑后取消Key:

if (key.isWritable()) {
    System.out.println("Writable");
    SocketChannel socketChannel = (SocketChannel) key.channel();
    States socketState = socketStates.get(socketChannel.hashCode());
    if (socketState == States.Read) {
        socketStates.put(socketChannel.hashCode(), States.Write);
        MyTask task = socketTasks.get(socketChannel.hashCode());
        System.out.println(task.getTimeToRead() + " " + task.getTimeToWrite());
        Runnable h = new MyAsyncWriteThread(task);
        pool.execute(h);
        // 仅处理完成后取消Key
        key.cancel();
        socketTasks.remove(socketChannel.hashCode());
    }
    // 移除原有的无条件key.cancel()
}

2. MyTask作用域错误,Write阶段无法获取正确参数

每次迭代SelectionKey时都会新建MyTask实例,Read阶段设置的任务参数仅存在于当前迭代的Task中,Write阶段触发时已是新的空Task,导致参数丢失、逻辑异常。

修复:新增Map存储每个通道对应的Task:

// 新增成员变量
private Map<Integer, MyTask> socketTasks = new HashMap<>();

// Read阶段存储Task
socketStates.put(socketChannel.hashCode(), States.Read);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
    int readBytes = socketChannel.read(byteBuffer);
    if (readBytes == -1) {
        // 客户端关闭时清理
        socketStates.remove(socketChannel.hashCode());
        key.cancel();
        socketChannel.close();
        return;
    }
    byteBuffer.flip();
    String result = new String(byteBuffer.array(), 0, byteBuffer.remaining()).trim();
    String[] words = result.split(" ");
    int secondsToRead = Integer.parseInt(words[words.length - 2])*1000;
    int secondsToWrite = Integer.parseInt(words[words.length - 1])*1000;
    task.setTimeToRead(secondsToRead);
    task.setTimeToWrite(secondsToWrite);
    socketTasks.put(socketChannel.hashCode(), task); // 绑定通道与Task
    System.out.println(task.getTimeToRead() + " " + task.getTimeToWrite());
    Runnable h = new MyAsyncReadThread(task);
    pool.execute(h);
    socketChannel.register(selector, SelectionKey.OP_WRITE);
} catch (Exception e) {
    System.out.println("Closing Connection Read...");
    socketStates.remove(socketChannel.hashCode());
    socketTasks.remove(socketChannel.hashCode());
    key.cancel();
    socketChannel.close();
}

3. 初始注册OP_WRITE导致Selector空转

客户端连接时直接注册OP_READ + OP_WRITE,但SocketChannel刚建立时写缓冲区默认空闲,会持续触发可写事件,导致Selector频繁被唤醒,消耗资源且干扰正常事件处理。

修复:初始仅注册OP_READ,需要写时再注册OP_WRITE:

// 客户端连接时
socketChannel.register(selector, SelectionKey.OP_READ);
// Read阶段处理完成后,再注册OP_WRITE
socketChannel.register(selector, SelectionKey.OP_WRITE);

4. Read操作未处理返回值,存在数据读取异常

未判断socketChannel.read(byteBuffer)的返回值,当客户端关闭连接时read返回-1,会导致后续解析参数报错;同时未切换缓冲区为读模式,可能读取到无效数据。

修复:处理Read返回值并切换缓冲区模式:

int readBytes = socketChannel.read(byteBuffer);
if (readBytes == -1) {
    socketStates.remove(socketChannel.hashCode());
    socketTasks.remove(socketChannel.hashCode());
    key.cancel();
    socketChannel.close();
    return;
}
byteBuffer.flip(); // 切换为读模式
String result = new String(byteBuffer.array(), 0, byteBuffer.remaining()).trim();

5. 异常场景未清理状态资源

Read阶段的catch块仅打印日志,未清理socketStates和socketTasks中的残留数据,也未关闭通道,导致无效状态累积。

修复:在catch块中补充资源清理:

catch (Exception e) {
    System.out.println("Closing Connection Read...");
    socketStates.remove(socketChannel.hashCode());
    socketTasks.remove(socketChannel.hashCode());
    key.cancel();
    socketChannel.close();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 15:35:23