学习NIO——Selector

现在我们看看关于NIO三大组件之一的Selector究竟做了些什么?简单来说,Selector就是“维护“另外一个组件Channel的。从Channel注册到最终注销整个生命周期将由Selector间接管理。

那么上述说到的”维护“和间接究竟是什么意思呢?我们先来看看Selector本身的一些属性——

维护三个集合

Selector维护有三个集合,他们分别如下——

  • Key Set:所有注册到Selector中的SelectionKey集合
  • Selected Key:有事件发生的SelectionKey集合
  • Cancelled Key:被取消SelectionKey集合

注意:以上的Selected KeyCancelled Key均为Key Set的子集。

接下来我们从Channel的”生命周期“来研究Selector的作用

Channel的“生命周期”

1. 注册

一个Channel想要使用NIO的体系,就必须注册到Selector中进行管理。Channel通过调用SelectableChannel#register(Selector sel, int ops)进行注册,后面的参数表明此Channel感兴趣的I/O操作类型。

① Channel注册的时候会生成一个与之相关联的SelectionKey对象,此对象将会后续被Selector进行管理和维护。SelectionKey中含有Channel和他感兴趣I/O操作类型的信息,因此管理SelectionKey就相当于是管理了Channel,这也就是我们上文中提及的”间接“管理的含义了。

② 生成的SelectionKey将会被添加到上述Selector维护的三个集合中的第一个集合Key Set中。

至此注册的过程就已经完成。

2. 通信

这个过程是I/O中最重要的一环,就是通过这个Channel和客户端进行交流。

Selector通过调用Selector#select()或者类似方法获取已经准备好进行I/O操作(连接、接受连接、读、写)的Channel,注意此时选出的Channel是依照注册时设置的感兴趣IO事件获取的已经准备好进行I/O操作的(由底层操作系统完成)。

根据以上的体系,我们知道已经备好I/O操作Channel将会被Selector纳管到Selected Key这个集合中。我们需要首先判断Channel对应的SelectionKey是否已经存在于Selected Key中,然后再进行下一步的操作。

  • 如果Channel对应的SelectionKey不存在于Selected Key集合中,首先将Channel对应的SelectionKey添加到Selected Key;第二步:先清除SelectionKey中ready-operation内容,然后向其中新增本次Channel的I/O操作类型;(具体的新增方法我们等会提及)
  • 如果Channel对应的SelectionKey已经存在于Selected Key集合。那么只需要更新SelectionKey中ready-operation的I/O操作类型即可。

如果key set中所有的SelectionKey都没有感兴趣的IO事件,那么Selected key和ready-operation将都不会更新

SelectinoKey其中其实也维护了一个”集合“,说是集合,但实际上是通过一个整数来存储的。这个集合就是ready set也就是上面的ready-operation,用于存放Channel中已经准备好的I/O类型。在更新的时候就是将新的就绪事件和ready set做或运算得到新的ready set

接下来看看SelectionKey是怎样维护I/O操作类型的。我们看看SelectionKey中对于事件是这样定义的——

public static final int OP_READ = 1 << 0;   		// 0000 0001 
public static final int OP_WRITE = 1 << 2;			// 0000 0100
public static final int OP_CONNECT = 1 << 3;		// 0000 1000
public static final int OP_ACCEPT = 1 << 4;			// 0001 0000

我们直接看可能一时无法理解,但是我在后面给大家写出了他们的二进制,我们观察到这些不同的I/O类型的二进制中只有一个1,并且这个1的位置也不同,基于这样的特征,对于一个ready set我们可以判断该数字的某一位是否为1进而判断是否有相应的I/O类型已经准备好。

在二进制运算中通过ready set和IO类型做”与“运算(具体逻辑大家可以画一画),如果结果恰好等于IO类型对应的数值;那么就说明原始的ready set中IO类型那一位为1。所以我们可以看看SelectionKey中判断是否有对应事件的实现过程——

// 判断读事件是否就绪的实现逻辑,其他的类型一样
public final boolean isReadable() {
    return (readyOps() & OP_READ) != 0; // readyOps()就是获取ready set的一个方法
}

通过以上的过程就将I/O就绪的Channel选出来了。

注意:在从selected key中取出对应的Channel进行消费之后,还需要手动将SelectionKey从selected key中移除。因为消费之后不会自动从selected key这个集合中删除。

2. 注销

当Channel被关闭的时候,因为Selector并不是直接维护Channel,因此还需要一些后续的收尾工作要完成。首先就是Channel对应的SelectionKey将会被放入到Selector维护的Cancelled key中,等待下一次Selector调用select的时候对这个集合进行清理。

原文地址:https://www.cnblogs.com/micadai/p/13584139.html