关键在于统一注册不同通道到同一Selector并设为非阻塞:ServerSocketChannel注册OP_ACCEPT,SocketChannel注册OP_READ/OP_WRITE;select循环中按channel类型和isXXX()区分处理,并动态调整interestOps,处理完key需remove()。

关键在于让不同类型的通道(如 ServerSocketChannel 和 SocketChannel)统一注册到同一个 Selector,并为每种通道设置恰当的事件兴趣集,再在一次 select() 循环中按 SelectionKey 类型分别处理。
确保所有通道都处于非阻塞模式
这是使用 Selector 的硬性前提。只有 SelectableChannel 子类(如套接字通道)支持非阻塞,FileChannel 不可注册。
-
ServerSocketChannel必须调用configureBlocking(false) -
SocketChannel在accept()后也必须立即设为非阻塞 - 每个通道注册前未设非阻塞会抛出
IllegalBlockingModeException
为不同通道注册对应的兴趣事件
同一 Selector 可同时管理监听连接的服务器通道和已建立连接的数据通道,但它们关注的事件不同:
-
ServerSocketChannel只注册OP_ACCEPT—— 它只负责接收新连接 -
SocketChannel接收后注册OP_READ(通常还需按需动态添加/移除OP_WRITE) - 若需同时读写,可用位或组合:
OP_READ | OP_WRITE,但注意OP_WRITE就绪频繁,应避免长期注册以免空轮询
在单次 select 循环中区分处理各类就绪事件
每次 select() 返回后,遍历 selectedKeys(),通过 key.channel() 类型和 key.isXXX() 判断具体行为:
- 若
key.channel()是ServerSocketChannel且isAcceptable()→ 调用accept(),获取新SocketChannel,设非阻塞并注册OP_READ - 若
key.channel()是SocketChannel且isReadable()→ 从缓冲区读数据,业务逻辑处理 - 若
key.channel()是SocketChannel且isWritable()→ 写入待发数据,写完后通常取消OP_WRITE注册(除非还有剩余) - 务必在处理完每个
key后调用keyIterator.remove(),否则下次select()仍会返回它
动态调整兴趣集以适配状态变化
OP_WRITE 的典型用法不是常驻注册,而是按需开关:
- 当尝试写入但
channel.write()返回 0(内核发送缓冲区满),说明暂时不可写,此时应注册OP_WRITE等待就绪 - 当
isWritable()触发后完成写入,且缓冲区清空,应调用key.interestOps(OP_READ)移除OP_WRITE,回归读等待状态 - 可通过
key.interestOps(newOps)或key.interestOps(key.interestOps() | OP_WRITE)动态更新










