Java NIO Selector - tenji/ks GitHub Wiki

Java NIO Selector

Java NIO 选择器是一个组件,它可以检查一个或多个 Java NIO Channels 实例,并确定哪些通道已准备好用于例如读(reading)或写(writing)。这样,单个线程可以管理多个通道,从而管理多个网络连接。

Selector 相当于 Reactor 模式中的什么角色?

Selector 相当于 Reactor 模式中多路复用器(Demultiplexer)的角色,职责是在句柄(Handle)集合上阻塞等待事件的发生,当检测到新事件时,通知初始分发器回调事件处理器。更多关于 Reactor 模式,传送门

一、为什么使用选择器?

仅使用单个线程来处理多个通道的优点是需要更少的线程来处理通道。实际上,你可以仅使用一个线程来处理所有通道。对于操作系统来说,线程之间的切换是昂贵的,并且每个线程也会占用操作系统中的一些资源(内存)。因此,使用的线程越少越好。

二、创建选择器

可以通过调用 Selector.open() 方法创建一个选择器,如下所示:

Selector selector = Selector.open();

三、注册通道(Channels)

为了将 Channel 与 Selector 一起使用,你必须向 Selector 注册 Channel。这是使用 SelectableChannel.register() 方法完成的,如下所示:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

四、SelectionKey

当你向 Selector 注册 Channel 时,register() 方法会返回 SelectionKey 对象。SelectionKey 一直有效,直到通过调用它的 cancel 方法、关闭它的通道或关闭它的选择器来取消它。取消 Key 不会立即将其从其选择器中删除;而是将其添加到选择器的取消键集(cancelled-key set)中,以便在下一次选择操作中删除。可以通过调用 Key 的 isValid 方法来测试 Key 的有效性。

这个 SelectionKey 对象包含下面一些属性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

4.1 The interest set

兴趣集(Interest Set)是你对“selecting”感兴趣的事件集,如上面“注册通道”部分中所述。你可以通过 SelectionKey 读取和写入该兴趣集,如下所示:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = SelectionKey.OP_ACCEPT  == (interests & SelectionKey.OP_ACCEPT);
boolean isInterestedInConnect = SelectionKey.OP_CONNECT == (interests & SelectionKey.OP_CONNECT);
boolean isInterestedInRead    = SelectionKey.OP_READ    == (interests & SelectionKey.OP_READ);
boolean isInterestedInWrite   = SelectionKey.OP_WRITE   == (interests & SelectionKey.OP_WRITE);

正如你所看到的,你可以将兴趣集(Interest Set)与给定的 SelectionKey 常量进行 AND 操作,以查明某个事件是否在兴趣集中。

4.2 The ready set

就绪集(Ready Set)是通道已准备好的操作集。你将主要在选择后访问准备好的集合。你可以像这样访问 Ready Set:

int readySet = selectionKey.readyOps();

你可以参考上面判断某个事件是否在兴趣集中一样,判断某个事件是否在就绪集中。但是,你也可以使用这四种方法,它们都返回一个布尔值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

4.3 Channel + Selector

从 SelectionKey 访问通道 + 选择器非常简单。其实现方式如下:

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();

4.4 An attached object (optional)

以下是 attach 对象的方法:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

你还可以在使用 register() 方法向选择器注册 Channel 时附加一个对象。看起来是这样的:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

SelectionKey 相当于 Reactor 模式中的什么角色?

Channels 相当于 Reactor 模式中的句柄(Handle)的角色?SelectionKey 句柄(Handle)事件载体的角色吗?

五、通过选择器选择通道

如果你对可读的通道感兴趣,您将从 select() 方法接收可读的通道。以下是 select() 方法:

  • int select()

    阻塞,直到至少有一个通道准备好了你注册的事件。

  • int select(long timeout)

    与 select() 的作用相同,只是它会阻塞最多 timeout 毫秒(参数)。

  • int selectNow()

    根本不阻塞。无论通道准备就绪,它都会立即返回。

select() 方法返回的 int 表明有多少个通道已准备好。也就是说,自上次调用 select() 以来有多少个通道已准备就绪。

5.1 selectedKeys()

一旦调用了 select() 方法之一并且其返回值表明一个或多个通道已准备就绪,你就可以通过调用选择器 selectedKeys() 方法通过 "selected key set" 访问就绪通道。如下:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

六、wakeUp()

当调用 select() 方法的线程被阻塞(blocked),有方法可以使其退出 select() 方法,即使当前没有通道处于就绪状态。这是通过让另一个线程在第一个线程调用 select() 的 Selector 上调用 Selector.wakeup() 方法来完成的。然后,在 select() 内等待的线程将立即返回。

七、代码示例

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

while(true) {

    int readyChannels = selector.selectNow();
    
    if(readyChannels == 0) 
        continue;
    
    Set<SelectionKey> selectedKeys = selector.selectedKeys();

    Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

    while(keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();

        if(key.isAcceptable()) {
            // a connection was accepted by a ServerSocketChannel.
        } else if (key.isConnectable()) {
            // a connection was established with a remote server.
        } else if (key.isReadable()) {
            // a channel is ready for reading
        } else if (key.isWritable()) {
            // a channel is ready for writing
        }

        keyIterator.remove();
    }
}

∞、参考链接