欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java NIO 简例】Selector

程序员文章站 2022-03-07 19:20:13
...

原文:《Java NIO Selector

Selector 可以检查多个 Channel 实例,发现那些已经就绪,可以读/写的 Channel。
通过这个机制,可以实现 单线程处理多个Channel,从而处理多个网络连接

 

为什么要使用 Selector ?

对操作系统来说,线程之间的切换代价较高,而且每个线程都会占用一些内存资源。所以线程越少越好。
而利用Select可以实现只用一个线程处理多个 Channel。

现代操作系统和CPU在多任务处理方面越来越强,多线程开销也更小了。事实上,对于一个多核CPU,如果不采用多任务,可能就是在浪费CPU的算力。但这些属于另一个话题范畴。


【Java NIO 简例】Selector
            
    
    博客分类: Java nio 

 

创建 Selector

Selector selector = Selector.open();

 

将 Channel 注册到 Selector

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
  • 此处的 Channel 必须设置为 非阻塞 模式。
    FileChannel 没有非阻塞模式,所以它不能与 Selector 共用;SocketChannel 有非阻塞模式,所以它可以。
  • SelectionKey.OP_READ 表示需要监听 Channel 的 Read 事件。
    即,当 Channel 可以被读取时,Selector 会将 OP_READ 加入相应 SelectionKey 的 “已就绪操作集合”中,并把该 SelectionKey 加入 Selector 的 selected-key 集合,供后续操作使用。
Channel 事件及对应的常量值 Channel 事件 常量值 含义
Connect SelectionKey.OP_CONNECT  完成与远程服务器的连接(或连接出错)
Accept SelectionKey.OP_ACCEPT 连接被接受(或出错) 
Read SelectionKey.OP_READ

“读操作”准备就绪

或 已读到数据流的末尾

或 Channel 被连接的另一端关闭

或 出错

Write SelectionKey.OP_WRITE

“写操作” 准备就绪

或 Channel 被连接的另一端关闭

或 出错

 

如果需要监听多个事件,可以用“或”操作连接相应的常量值。如:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

 

SelectionKey

SelectionKey 包含的信息主要有:

  • 监听事件类型集合(Interest Set)
  • 已就绪事件类型集合(Ready Set)
  • Channel
  • Selector
  • 附带的对象(可选)

Interest Set

可通过 SelectionKey.interetOps() 获取该集合,并通过“位操作”来判断是否注册监听了某个事件。

int interestSet = selectionKey.interestOps();

boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

 

Ready Set

可通过 SelectionKey.readyOps() 获取该集合。
可以像 Interest Set 那样通过“位操作”来判断某个事件是否已发生。
也可以直接调用 SelectionKey 对象相应的方法来判断(内部原理相同)。

int readySet = selectionKey.readyOps();

selectionKey.isConnectable();
selectionKey.isAcceptable();
selectionKey.isReadable();
selectionKey.isWritable();

 

Channel 和 Selector

可通过 SelectionKey 对象相关的方法来获取其对应的 Channel 和 Selector

Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();

 

附带对象

可将某个对象附带到 SelectionKey 对象上,为后续处理数据提供方便。如,把处理 Channel 数据的 Buffer 对象附带到key上。

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

// 也可在注册Selector时指定附带对象
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, obj);

 

从 Selector 获取就绪的 Channel

可通过 Selector 对象的以下3个方法之一得知是否有 Channel 已就绪。这三个方法都会返回 int,表示已就绪 Channel 的数量

  • select() 阻塞,直到至少有一个 Channel 就绪
  • select(long timeout) 阻塞,直到至少有一个 Channel 就绪 或 超时
  • selectNow() 非阻塞。无论当前有没有 Channel 就绪,都会立即返回

selectedKeys()

如果上述select方法返回的值大于0,即,有Channel就绪,就可以通过 selector 对象的 selectedKeys() 方法获取这些 Channel 对应的 SelectionKey。再通过 SelectionKey 实例获取 Channel 实例并执行后续操作。

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
  SelectionKey key = keyIterator.next();
  // 处理Channel
  keyIterator.remove();
}

 

Selector.wakeup()

如果一个线程调用了 Selector.select() 方法并发生阻塞,此时另一个线程调用了该 Selector 对象的 wakeup() 方法,则前一个被阻塞的线程将会立即返回。

如果调用 wakeup() 时,没有线程阻塞在 select() 方法,则下一个调用 select() 方法的线程将立即返回。除非期间调用了 selectNow() 方法

 

Selector.close()

如果调用了 Selector 对象的 close() 方法,则 Selector 对象会被关闭,其中的 SelectionKey 实例将失效,但 Channel 不会被关闭

 

完整的 Selector 示例

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while (true) {
  int readyChannelCount = selector.selectNow();
  if (0 == readyChannelCount) {
    continue;
  }

  Set selectedKeys = selector.selectedKeys();
  Iterator keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()){
      // 连接已被一个 ServerSocketChannel 接受
    } else if (key.isConnectable()) {
      // 已与远程服务器建立连接
    } else if (key.isReadable()) {
      // “读操作”已准备就绪
    } else if (key.isWritable()) {
      // “写操作”已准备就绪
    }

    keyIterator.remove();
  }
}
  • 【Java NIO 简例】Selector
            
    
    博客分类: Java nio 
  • 大小: 12.6 KB
相关标签: nio