关键在于按事件类型严格匹配操作:op_read仅读取并规范管理缓冲区,op_write仅用于恢复挂起写操作,须避免误注册和重复处理。

关键不是“看到就绪就立刻操作”,而是按事件类型严格匹配动作,并配合附件绑定与键清理,才能避免重复处理、资源泄漏或逻辑错乱。
读就绪(OP_READ)必须只做读,且注意缓冲区管理
当 key.isReadable() 返回 true,说明内核缓冲区有数据可拷贝到用户空间。此时只能调用 channel.read(buffer),不能写、不能重连、不能注册其他事件。
- 务必先检查 buffer 是否有足够剩余空间(
buffer.hasRemaining()),否则 read() 可能返回 0 或抛异常 - 读完后要调用
buffer.flip()切换为读模式,再解析内容;之后记得buffer.clear()或compact()为下一次读做准备 - 如果 read() 返回 -1,表示对端已关闭连接,应立即取消 key 并关闭 channel
写就绪(OP_WRITE)是“可写”而非“必须写”,需按需启用和关闭
key.isWritable() 触发时,仅表示通道当前不阻塞写入——但它通常是因为之前 write() 返回值小于待写字节数(即 TCP 窗口满),导致写操作被挂起。因此 OP_WRITE 是恢复写能力的信号,不是常规写入口。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 不要在连接刚建立时就注册 OP_WRITE;一般只在有数据待发且 write() 返回值
- 一旦成功写完全部数据,应立即调用
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE)取消写关注,防止空转触发 - 写操作中若遇到 IOException(如连接重置),需捕获并主动 cancel key,清理关联资源
用 attachment 绑定状态,避免跨事件丢失上下文
每次 select() 返回的 SelectionKey 是同一个对象,但其附件(attachment)是你唯一可安全携带会话信息的地方。读写事件可能交替触发,靠 channel 查找 session 容易出错或加锁。
- 注册时就绑定:例如
key.attach(new ConnectionContext(channel)) - 所有事件处理中统一取用:
ConnectionContext ctx = (ConnectionContext) key.attachment() - attachment 中建议只放轻量对象(如 ByteBuffer、协议解析器引用、请求 ID),不要放未关闭的流或线程池等长生命周期资源
遍历 selectedKeys 时必须用 Iterator.remove(),不能跳过清理
selectedKeys() 返回的是一个内部可变集合,key 不手动移除就会在下一轮 select() 中反复出现,造成同一条数据被多次读取、同一响应被重复发送等问题。
- 必须用
Iterator<selectionkey> iter = selector.selectedKeys().iterator()</selectionkey>遍历 - 每个 key 处理完毕后,立刻执行
iter.remove()—— 放在 try-finally 块里最稳妥 - 禁止使用 for-each 循环,它底层调用 iterator() 但无法控制 remove 行为,容易引发 ConcurrentModificationException
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










