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);
NIO与Selector详解

943

被折叠的 条评论
为什么被折叠?



