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
相关产品推荐
相关产品推荐

