应调用selectionkey.isreadable()判断通道是否可读,它检查就绪集而非兴趣集;需先验证key.isvalid(),再处理read()返回值(-1表示关闭,0表示无新数据)。

如何用 SelectionKey 检查通道是否可读
直接看 SelectionKey.isReadable() 是最常用、也最可靠的判断方式。它返回 true 表示该 key 对应的通道在最近一次 Selector.select()(或 selectNow())后被标记为“就绪读”,也就是底层有数据可读、或连接已关闭、或发生 EOF。
OP_READ 和 isReadable() 的关系容易搞混
SelectionKey.OP_READ 是你注册时请求的**兴趣集(interest set)**,表示“我想知道这个通道什么时候能读”;而 isReadable() 是运行时检查**就绪集(ready set)** 是否包含读就绪状态——两者不是一回事,不能互相替代。
- 即使你没注册
OP_READ,isReadable()也可能返回true(比如之前注册过,后来没改 interest set,但 ready set 还没被消费) - 如果只注册了
OP_WRITE,那就算 socket 缓冲区里有数据,isReadable()也不会变true—— 因为 selector 根本不关心读事件 - 调用
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE)才会真正让 selector 开始监听读就绪
为什么有时 isReadable() 为 true 却读不到数据?
常见于 TCP 连接被对端正常关闭(FIN 包到达),此时内核会把连接标记为“可读”,但实际 channel.read(buf) 会返回 -1(EOF),而不是抛异常。这是合法且预期的行为,不是 bug。
- 务必检查
read()返回值:-1 表示连接关闭,0 表示暂无新数据(非阻塞模式下可能),>0 才是真实读到字节 - 不要仅靠
isReadable()就循环调用read(),否则可能陷入空转(尤其在非阻塞模式下) - 如果通道是
SocketChannel且处于阻塞模式,read()可能阻塞——但通常你不该在 selector 线程里用阻塞通道
别漏掉 SelectionKey.isValid() 检查
key 可能在处理过程中被取消(比如调用 key.cancel())或 channel 关闭导致 key 失效。如果跳过这步,后续调用 isReadable() 会抛 CancelledKeyException。
- 每次从
selectedKeys()遍历时,第一件事应该是if (!key.isValid()) continue; - 紧接着再判断
if (key.isReadable()) { ... } - 注意:
isValid()为false时,isReadable()的行为未定义,JVM 实现可能直接抛异常
实际轮询逻辑中,最容易被忽略的是就绪状态和兴趣集的分离,以及 key 有效性验证——这两点出问题,轻则逻辑跳过,重则线程崩溃。










