NIO之Selector详解

文章目录

        • 1. Selector的创建
        • 2. 向Selector注册Channel
        • 3. SelectionKey
          • 3.1 interest集合
          • 3.2ready集合
        • 4. Selector选择通道
        • 5. 唤醒和关闭 Selector
        • 6. Selector使用流程

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

1. Selector的创建

通过调用Selector.open()方法创建一个Selector,如下:

Selector selector = Selector.open();

2. 向Selector注册Channel

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

与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

Connect SelectionKey.OP_CONNECT

Accept SelectionKey.OP_ACCEPT

Read SelectionKey.OP_READ

Write SelectionKey.OP_WRITE

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

3. SelectionKey

此对象表示Channel 在 Selector 中的注册的标记。当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:

  • interest集合
  • ready集合
  • Channel
  • Selector
  • 附加的对象(可选)
3.1 interest集合

就像向Selector注册通道中所描述的,interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合

3.2ready集合

ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。
int readySet = selectionKey.readyOps();
可以使用以下四个方法,它们都会返回一个布尔类型:

1.selectionKey.isAcceptable();

2.selectionKey.isConnectable();

3.selectionKey.isReadable();

4.selectionKey.isWritable();

从SelectionKey访问Channel和Selector很简单。如下:

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();

附加的对象

可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

还可以在用register()方法向Selector注册Channel的时候附加对象。如:

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

4. Selector选择通道

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

select()阻塞到至少有一个通道在你注册的事件上就绪了。

select()方法返回的int值表示有多少通道已经就绪。自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。

一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()中的就绪通道集合。如下所示:
Set selectedKeys = selector.selectedKeys();

while (selector.select() > 0) {
	Set<SelectionKey> keys = selector.selectedKeys();
	Iterator<SelectionKey> iterator = keys.iterator();
	while (iterator.hasNext()) {
	   SelectionKey key = iterator.next();
	   iterator.remove();
	   if (key.isAcceptable()) {
	   ...
	   } else if (key.isReadable() && key.isValid()) {
	   ...
	   }
	   keys.remove(key);
	}
 }

注意, 在每次迭代时, 我们都调用 “keyIterator.remove()” 将这个 key 从迭代器中删除, 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中, 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.

5. 唤醒和关闭 Selector

某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。

如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)。

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

6. 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 中.如果是读事件,从key中获取Channel,进行读操作。

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

你可能感兴趣的:(Java,IO)