NIO基础(三)之Selector

概述

Selector , 选择器,也被称为多路复用器、轮询代理器等。它是 Java NIO 核心组件中的一个,用于轮询一个或多个Channel 的状态(连接完成、接收连接、可读、可写)。如此,一个线程就可以管理多个 Channel 。

那么 Selector 是如何轮询的呢?

首先,需要将 Channel 和 它感兴趣的事件注册到 Selector 中,这样 Selector 才知道哪些 Channel 的哪些事件是它需要管理的。
之后,Selector 会不断地轮询注册在其上的 Channel 。如果某个 Channel 上面发生了感兴趣的事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续操作。

在这里插入图片描述
Selector 和 SelectionKey 关系紧密,阅读下文介绍的这2个类时,可以交替阅读。

Selector类

在这里插入图片描述
在这里插入图片描述

创建 Selector

通过静态方法来创建对象。

Selector selector = Selector.open();

通过 Selector 选择 SelectionKey

返回值不是重点,主要是阻塞的作用。

// 阻塞到至少有一个 Channel 在你注册的事件上就绪了。
public abstract int select() throws IOException;

// 在 `#select()` 方法的基础上,增加超时机制。
public abstract int select(long timeout) throws IOException;

// 和 `#select()` 方法不同,立即返回数量,而不阻塞。
public abstract int selectNow() throws IOException;

select 方法返回的 int 值,表示经过这次 select 有多少 Channel 已经就绪。

获取已就绪的 SelectionKey 集合

一旦调用了 select 的阻塞方法,并且有值返回,说明有新增的就绪 Channel 了。然后可以通过调用Selector 的 selectedKeys() 方法,得到所有 select 出来的 SelectionKey集合。

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

一般会将Set转换为迭代器Iterator处理。

Iterator<SelectionKey> it = selectedKeys.iterator();
    SelectionKey key ;
    while(it.hasNext()){
        key = it.next();
        /*我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。
        如果我们没有删除处理过的键,那么它仍然会在事件集合中以一个激活
        的键出现,这会导致我们尝试再次处理它。*/
        it.remove();
        try {
        	//自定义处理方法
            handleInput(key);
        } catch (Exception e) {
            if(key!=null){
                key.cancel();
                if(key.channel()!=null){
                    key.channel().close();
                }
            }
        }

唤醒 Selector 选择

某个线程调用 select() 方法后,发生阻塞,即使没有Channel就绪,也有办法让其从 select() 方法返回。

只要让其它线程在第一个线程调用 select() 方法的那个 Selector 对象上,调用该 Selector 的 wakeup() 方法即可。

public abstract Selector wakeup();

Selector 的 select(long timeout) 方法,若未超时的情况下,也可以满足上述方式。

注意:如果有其它线程调用了wakeup() 方法,但当前线程没有阻塞在 select() 方法上时,下次调用 select() 方法时会被立即唤醒。

关闭 Selector

调用 Selector 的 close() 方法,可以将其进行关闭。

public abstract void close() throws IOException;

Selector 相关的所有 SelectionKey 都会失效,但相关的所有 Channel 并不会关闭。
并且阻塞方法select()也会返回。

SelectionKey类

SelectionKey是一个抽象类,表示 Channel 在Selector中注册的标识,每个 Channel 向 Selector 注册时,都将会创建一个SelectionKey 。
在这里插入图片描述
在这里插入图片描述

注册 Chanel 到 Selector 中

// channel的创建这里没有提供
channel.configureBlocking(false); //设置为非阻塞
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

FileChannel 是不能够注册到 Selector 中的,因为它只能是阻塞的。

4种事件

注册的时候可以通过SelectionKey类里的4个静态不可变整数来确定当前 Channel 感兴趣的事件。

  • Connect :连接完成事件( TCP 连接 ),仅适用于客户端,对应 SelectionKey.OP_CONNECT 。
  • Accept :接受新连接事件,仅适用于服务端,对应 SelectionKey.OP_ACCEPT 。
  • Read :读事件,适用于两端,对应 SelectionKey.OP_READ ,表示 Buffer 可读
    Buffer写满了才处于Buffer可读,所以该事件经常被注册,仅当就绪时才发起读操作,避免浪费CPU。
  • Write :写事件,适用于两端,对应 SelectionKey.OP_WRITE ,表示 Buffer 可写
    Buffer直到写满之前都处于Buffer可写,没必要注册该事件,否则该条件不断就绪浪费CPU。但如果是写密集型的任务,比如文件下载等,Buffer很可能满,注册该操作类型就很有必要,同时注意写完后取消注册。

因为4个事件是通过1的移位来表示的,所以4个状态可以通过“|”操作符来进行相加,表示对多个事件感兴趣。

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

cancel和isValid

可以通过cancel方法取消键(key),取消的键不会立即从selector中移除,而是添加到cancelledKeys中,在下一次select操作时移除它。所以在调用某个key时需要使用isValid进行校验。

从SelectorKey中拿到Channel

	//即使key被取消了也能拿到
    public abstract SelectableChannel channel();

判断对哪些事件感兴趣

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT != 0;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT != 0;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ != 0;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE != 0;

设置对哪些事件感兴趣

除了注册的时候,和再次注册修改的时候,还有如下方法。

public abstract SelectionKey interestOps(int ops);

判断事件是否就绪

int readySet = selectionKey.readyOps();
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

源码:

public final boolean isReadable() {
    return (readyOps() & OP_READ) != 0;
}
public final boolean isWritable() {
    return (readyOps() & OP_WRITE) != 0;
}
public final boolean isConnectable() {
    return (readyOps() & OP_CONNECT) != 0;
}
public final boolean isAcceptable() {
    return (readyOps() & OP_ACCEPT) != 0;
}

attach和attachment

通过调用 attach(Object ob) 方法,可以向 SelectionKey 添加附加对象。

selectionKey.attach(theObject);

通过调用 attachment() 方法,可以获得 SelectionKey 获得附加对象。

Object attachedObj = selectionKey.attachment();

在注册时,可以直接添加附加对象。

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
©️2020 CSDN 皮肤主题: 岁月 设计师: pinMode 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值