Java NIO之Selector类与SelectableChannel类

Selector类

简介

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

官方文档的解释:SelectableChannel 对象的多路复用器 可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。也可通过调用自定义选择器提供者的 openSelector 方法来创建选择器。通过选择器的 close 方法关闭选择器之前,它一直保持打开状态。

主要方法
public static Selector open()
                     throws IOException打开一个选择器。 
通过调用系统级默认 SelectorProvider 对象的 openSelector 方法来创建新的选择器。 


返回:
新的选择器 
抛出: 
IOException - 如果发生 I/O 错误
selectedKeys
public abstract Set<SelectionKey> selectedKeys()返回此选择器的已选择键集。 
可从已选择键集中移除键,但是无法直接添加键。试图向该键集中添加对象会导致抛出 UnsupportedOperationException。 

已选择键集是非线程安全的。 


返回:
此选择器的已选择键集 
抛出: 
ClosedSelectorException - 如果此选择器已关闭
select
public abstract int select(long timeout)
                    throws IOException选择一组键,其相应的通道已为 I/O 操作准备就绪。 
此方法执行处于阻塞模式的选择操作。仅在至少选择一个通道、调用此选择器的 wakeup 方法、当前的线程已中断,或者给定的超时期满(以先到者为准)后此方法才返回。 

此方法不提供实时保证:它安排了超时时间,就像调用 Object.wait(long) 方法一样。 


参数:
timeout - 如果为正,则在等待某个通道准备就绪时最多阻塞 timeout 毫秒;如果为零,则无限期地阻塞;必须为非负数 
返回:
已更新其准备就绪操作集的键的数目,该数目可能为零 
抛出: 
IOException - 如果发生 I/O 错误 
ClosedSelectorException - 如果此选择器已关闭 
IllegalArgumentException - 如果 timeout 参数的值为负
close
public abstract void close()
                    throws IOException关闭此选择器。 
如果某个线程目前正阻塞在此选择器的某个选择方法中,则中断该线程,如同调用该选择器的 wakeup 方法那样。 

所有仍与此选择器关联的未取消键已无效、其通道已注销,并且与此选择器关联的所有其他资源已释放。 

如果此选择器已经关闭,则调用此方法无效。 

关闭选择器后,除了调用此方法或 wakeup 方法外,以任何其他方式继续使用它都将导致抛出 ClosedSelectorException。 


抛出: 
IOException - 如果发生 I/O 错误

SelectableChannel

简介

可通过 Selector 实现多路复用的通道。

为了与选择器一起使用,此类的实例必须首先通过 register 方法进行注册。此方法返回一个表示该通道已向选择器注册的新 SelectionKey 对象。

向选择器注册后,通道在注销 之前将保持注册状态。注销涉及释放选择器已分配给该通道的所有资源。

不能直接注销通道;相反,必须取消 表示通道注册的键。取消某个键要求在选择器的下一个选择操作期间注销通道。可通过调用某个键的 cancel 方法显式地取消该键。无论是通过调用通道的 close 方法,还是中断阻塞于该通道上 I/O 操作中的线程来关闭该通道,都会隐式地取消该通道的所有键。

如果选择器本身已关闭,则将注销该通道,并且表示其注册的键将立即无效。

一个通道至多只能在任意特定选择器上注册一次。

可通过调用 isRegistered 方法来确定是否向一个或多个选择器注册了某个通道。

多个并发线程可安全地使用可选择的通道。

主要方法
register
public abstract SelectionKey register(Selector sel,
                                      int ops,
                                      Object att)
                               throws ClosedChannelException向给定的选择器注册此通道,返回一个选择键。 
如果当前已向给定的选择器注册了此通道,则返回表示该注册的选择键。该键的相关操作集将更改为 ops,就像调用 interestOps(int) 方法一样。如果 att 参数不为 null,则将该键的附件设置为该值。如果已取消该键,则抛出 CancelledKeyException。 

如果尚未向给定的选择器注册此通道,则注册该通道并返回得到的新键。该键的初始可用操作集是 ops,并且其附件是 att。 

可在任意时间调用此方法。如果调用此方法的同时正在进行另一个此方法或 configureBlocking 方法的调用,则在另一个操作完成前将首先阻塞该调用。然后此方法将在选择器的键集上实现同步,因此如果调用此方法时并发地调用了涉及同一选择器的另一个注册或选择操作,则可能阻塞此方法的调用。 

如果正在进行此操作时关闭了此通道,则此方法返回的键是已取消的,因此返回键无效。 


参数:
sel - 要向其注册此通道的选择器
ops - 所得键的可用操作集
att - 所得键的附件,可能为 null 
返回:
表示此通道向给定选择器注册的键 
抛出: 
ClosedChannelException - 如果此通道已关闭 
IllegalBlockingModeException - 如果此通道处于阻塞模式 
IllegalSelectorException - 如果此通道与给定的选择器不是由相同的提供者创建的 
CancelledKeyException - 如果此通道当前已向给定的选择器注册,但是相应的键已经被取消 
IllegalArgumentException - 如果 ops 集的某个位不对应于此通道所支持的某个操作,也就是说,如果 set & ~validOps() != 0
configureBlocking
public abstract SelectableChannel configureBlocking(boolean block)
                                             throws IOException调整此通道的阻塞模式。 
如果向一个或多个选择器注册了此通道,则尝试将此通道置于阻塞模式将导致抛出 IllegalBlockingModeException。 

可在任意时间调用此方法。新的阻塞模式仅影响在此方法返回后发起的 I/O 操作。对于某些实现而言,这可能需要在所有挂起的 I/O 操作完成之前阻塞其他操作。 

如果调用此方法的同时正在进行另一个此方法或 register 方法的调用,则在另一个操作完成前将首先阻塞该调用。 


参数:
block - 如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式 
返回:
此可选择通道 
抛出: 
ClosedChannelException - 如果此通道已关闭 
IllegalBlockingModeException - 如果 block 为 true 并且此通道已向一个或多个选择器注册 
IOException - 如果发生 I/O 错误

selector的使用

创建选择器

通过 Selector.open()方法, 我们可以创建一个选择器:

Selector selector = Selector.open();
将 Channel 注册到选择器中

为了使用选择器管理 Channel, 我们需要将 Channel 注册到选择器中:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
如果一个 Channel 要注册到 Selector 中, 那么这个 Channel 必须是非阻塞的, 即channel.configureBlocking(false);
因为 Channel 必须要是非阻塞的, 因此 FileChannel 是不能够使用选择器的, 因为 FileChannel 都是阻塞的.
获取可操作的 Channel

如果 select()方法返回值表示有多个 Channel 准备好了, 那么我们可以通过 Selected key set 访问这个 Channel:

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();
}

在每次迭代时, 我们都调用 "keyIterator.remove()" 将这个 key 从迭代器中删除, 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中, 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.
例如此时我们收到 OP_ACCEPT 通知, 然后我们进行相关处理, 但是并没有将这个 Key 从 SelectedKeys 中删除, 那么下一次 select() 返回时 我们还可以在 SelectedKeys 中获取到 OP_ACCEPT 的 key.
注意, 我们可以动态更改 SekectedKeys 中的 key 的 interest set. 例如在 OP_ACCEPT 中, 我们可以将 interest set 更新为 OP_READ, 这样 Selector 就会将这个 Channel 的 读 IO 就绪事件包含进来了.

Selector 的基本使用流程

  • 通过 Selector.open() 打开一个 Selector.

  • 将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set)

  • 不断重复:

    • 调用 select() 方法
    • 调用 selector.selectedKeys() 获取 selected keys
    • 迭代每个 selected key:
      • 从 selected key 中获取 对应的 Channel 和附加信息(如果有的话)

      • 判断是哪些 IO 事件已经就绪了, 然后处理它们. 如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.

      • 根据需要更改 selected key 的监听事件.

      • 将已经处理过的 key 从 selected keys 集合中删除.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值