Selector 中文为 选择器。在 Java 的 NIO 中,多个 Channel 可注册到同一个 Selector 中。Selector 通过轮询的方式查看 Channel 是否处于可读可写的状态。这样一个线程可以管理多个 Channel ,也就能管理多个网络连接,实现多路复用,故 Selector 也称为 多路复用器 。
下面是一个简单的使用示例:
try (ServerSocketChannel channel = ServerSocketChannel.open();
Selector selector = Selector.open();) {
channel.bind(new InetSocketAddress(9999));
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select() == 0) {
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
// do sth...
}
iterator.remove();
}
}
} catch (Exception e) {
e.printStackTrace();
}
通过 Selector.open()
方法创建 Selector :
Selector selector = Selector.open();
为了使 Selector 管理 Channel ,需要将 Channel 注册到 Selector 中,通过调用 SelectableChannel.register()
方法。
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
SelectableChannel
或者它的子类。register()
方法的第二个参数是 Selector 监听 Channel 时感兴趣的事件集合。可以监听下面四种事件:事件 | 常量 | 描述 |
---|---|---|
Read | SelectionKey.OP_READ | 读就绪事件,此时 Buffer 可读 |
Write | SelectionKey.OP_WRITE | 写就绪事件,此时 Buffer 可写 |
Connect | SelectionKey.OP_CONNECT | 连接完成事件 |
Accept | SelectionKey.OP_ACCEPT | 接收新的连接事件 |
一个 Channel 对多个事件感兴趣时,可以用或运算符组合:
channel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT);
向 Selector 注册一个 Channel 后,会返回一个 SelectionKey 对象,其保存了 Channel 与 Selector 的对应关系,具体如下:
同时提供了相应的方法获取上述属性,可调用 selectionKey.attach(theObject);
方法或在向 Selector 注册 Channel 时传入 attachment 附加对象,可用于方便识别特定 Channel 。
在向 Sector 注册 Channel 后,就可以调用 select()
方法返回感兴趣的事件已经就绪的通道数量。它有三个重载方法:
// 阻塞到至少有一个感兴趣的事件就绪,然后返回就绪 Channel 数量
public abstract int select() throws IOException;
// 阻塞直到超时
public abstract int select(long timeout) throws IOException;
// 不阻塞,立即返回
public abstract int selectNow() throws IOException;
当 select()
方法返回大于 0 时,即表示有通道已经就绪了,这时就可以调用 Selector.selectKeys()
方法获得 “已选择键集(selected key set)” 中就绪的 Channel 。
❤注意:
1、当有就绪的通道时,一定要先调用
select()
方法,才能够将其加入已选择键集,直接调用selectKeys()
方法是拿不到通道对应的 SelectionKey 对象的。
2、每次迭代末尾需要调用keyIterator.remove()
方法,Selector 不会自己从已选择键集中移除 SelectionKey 对象。。
关于 Selector 的源码实现可参考 :《Selector 实现原理》 。