NIO(4、Selector)

目录

NIO(1、概述)
NIO(2、Buffer)
NIO(3、Channel)
NIO(4、Selector)html

Selector

前面两个章节都描述了Buffer和Channel,那这个章节就描述NIO三个最核心部分的最后一块内容 - 选择器(Selector)
  java

如何使用

  在前面的章节中描述过多路复用,一个线程经过选择器处理和管理多个通道。因而可知,选择器是用来处理多个通道并监听其通道事件的组件。socket

  • Create
      只须要调用 open() 便可建立一个Selector对象:
Selector selector = Selector.open();
  • Register
      经过 register() 方法注册通道:
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);

  在注册通道以前,把通道设置成非阻塞模式,观察源码会发现 register() 会校验当前通道是否为非阻塞模式,当是阻塞模式时,会抛出IllegalBlockingModeException 异常。在前面一个章节也提过,为何FileChannel没有继承SelectableChannel,由于它不须要多路复用,因此在使用通道的时候,只有FileChannel不能向选择器注册通道,凡是继承SelectableChannel都可以向选择器注册通道。
  注册通道方法的第二个参数是SelectionKey中定义的操做类型,你能够填入任何你感兴趣的操做类型,只要这个通道支持,一样,在执行 register() 方法时也会校验该通道是否可以支持该操做。
  注册方法一样也会返回一个SelectionKey对象。线程

  • Attach Object
      注册通道的 register() 方法有一个重载方法,能够向选择器注册通道的时候,选择想要带上的附加对象:
public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;

  例如,使用时附加上一个字符串:rest

String ch_name = "123";
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT,ch_name);

  获取这个字符串可经过 attachment() 来获取:code

// 接收数据
String ch_name_accept = (String) selectionKey.attachment();

  固然,注册时返回的SelectionKey对象也能够在使用时候附加你想要的附加对象:htm

selectionKey.attach(ch_name);
  • Block
      由于是一个线程经过选择器来操做通道,那么选择器在操做通道时,一定在处理一个通道的时候,另外一个事件已就绪的通道处于等待状态。在肯定一个通道事件就绪以后,才能去操做这个通道。上文中讲到使用注册方法register使用的代码示例,将ServerSocketChannel对象向选择器注册,同时关注了这个通道的OP_ACCEPT操做类型事件,那么咱们何时能肯定该通道的accept事件就绪,能够操做这个通道了。选择器为咱们提供了三个重载的 select() 方法,这三个方法的主要功能就是选择是否阻塞直到该选择器中的通道所关注的事件就绪,来让程序继续往下运行。
      首先看 select() 方法,该方法会一直阻塞下去,直到选择器中的通道关注的事件就绪:
selector.select();

  参数5000是5秒,参数以毫秒为单位。这个方法会一直阻塞5秒,5秒以内若是没有通道事件就绪的话程序会往下运行:对象

selector.select(5000);

  selectNow()其实就是非阻塞,不管有无通道事件就绪,程序都会向下执行:blog

selector.selectNow();

  这三个方法的本质区别无非是选择器阻塞或者等待一个或多个通道的事件就绪有多长时间。继承

  • Keys & SelectionKeys
      咱们每为一个通道执行 register() 注册方法,就会返回一个SelectionKey,那么这个选择器全部已就绪的SelectionKey就是经过selectedKeys()来获取:
Set<SelectionKey> selectionKeys = selector.selectedKeys();

  通常这个方法是在select() 以后执行,由于到这一步就意味着要经过这个轮询每一个就绪的通道。

Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    if (key.isAcceptable()) {
        // 执行通道的操做
    }
    //执行完成移除
    iterator.remove();
}

  到这里说的是已就绪的通道,那么全部的 SelectionKey 集能够经过 keys() 方法获取:

Set<SelectionKey> keys = selector.keys();
  • wake up
      在使用Selector对象的 select() 或者 select(long) 方法时候,当前线程极可能一直阻塞下去,那么用另外一个线程去执行 Selector.wakeUp() 方法会唤醒当前被阻塞的线程,使其 select() 当即返回。
      固然,若是当前线程没有阻塞,那么执行了wakeUp() 方法以后,下一个线程的 select() 方法会被当即返回,再也不被阻塞下去。
  • close
      显然,close() 方法可以关闭当前的选择器。
      当一个线程当前呈阻塞状态,那么停止这种状态须要执行选择器的 wakeUp() 方法,close()方法的实现正是这么作的,先唤醒被阻塞的线程,而后继续接下来的操做。接下来就会会置空全部的通道、全部就绪的SelectionKey,让这个选择器上的轮询组件也闲置下来。

SelectionKey

  SelectionKey的功能相似于通道的一个注册令牌。
  这个类定义了4个操做类型,每种操做类型都对应了相应的事件,经过监听这几种不一样的事件,在触发该事件时表示所对应的操做已准备就绪:

操做类型 描述
OP_READ 1 << 0 读操做
OP_WRITE 1 << 2 写操做
OP_CONNECT 1 << 3 链接socket操做
OP_ACCEPT 1 << 4 接受socket操做

  这里得提一句,全部继承SelectableChannel的通道都会定义本身可以支持的操做类型,能够经过具体通道的 validOps() 方法查看,例如SocketChannel支持read、write、connect这几种操做:

// SocketChannel类 
public final int validOps() {
        return (SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT);
}
  • interestOps & readyOps
      这是SelectionKey的实现类中定义的变量:
private volatile int interestOps;
private int readyOps;

  interestOps用来存储感兴趣的操做集,readyOps用来存储已经就绪的操做集。
  其中 interestOps() 方法和 nioInterestOps() 都会返回interestOps,不一样的是interestOps()会校验是否已执行 cancel() ,若是已经取消则会抛出 CancelledKeyException 异常。readyOps一样也有 readyOps() 和 nioReadyOps() 方法,逻辑与interestOps几乎一致。
  观察这段SelectionKey抽象类已经实现的代码:

// SelectionKey 类
public final boolean isAcceptable() {
        return (readyOps() & OP_ACCEPT) != 0;
}

  当判断是否访问就绪的时候,只要 readyOps() 与相应的操做类型相与,非零就返回true,表明接受请求操做已就绪。这个是SelectionKey已提供的方法,可是SelectionKey并未提供一样返回boolean判断某个操做在interestOps集是否存在,咱们能够本身实现这些方法:

private boolean isInterestRead(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_READ;
}
private boolean isInterestWrite(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
}
private boolean isInterestConnect(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
}
private boolean isInterestAccept(SelectionKey selectionKey){
        return  (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
}
  • Channel、Selector
      在SelectionKey中获取通道或者选择器只须要调用其中的两个方法便可:
SelectableChannel selectableChannel = selectionKey.channel();
Selector sel = selectionKey.selector();
相关文章
相关标签/搜索