NIO06——Selector

 Java NIO 的 Selector 组件可以监测一个或多个 Channel 实例,可以监听到哪一个 Channel 为读写或者其他操作做好了准备。这样一个单线程就可以管理多个 Channel,相当于管理了多个网络连接。

 1、为什么使用 Selector ?
 使用一个线程去处理多个 Channel 的优势在于开启的线程少了,事实上,可以仅使用一个线程去处理所有的 Channel。操作系统在多个线程间切换时是很消耗资源的,并且每一个线程都会占用操作系统的资源(比如内存),因此开启的线程越少越好。

 但要注意,现代操作系统和CPU在多任务处理方面变得越来越好,多线程的开销也变得越来越小。事实上,如果一个CPU有多个内核,若不进行多任务处理反而浪费了CPU的功率。这不在本文的讨论范畴,这里需要知道的是,可以通过使用 Selector 而用一个单线程处理多个 Channel。如下图所示:
在这里插入图片描述
 2、创建 Selector
 可以通过调用 Selector.open() 方法创建一个 Selector:

Selector selector = Selector.open();

 3、向 Selector 中注册 Channel
 要想使用 Selector 处理或者说管理一个 Channel,必须将该 Channel 注册到这个 Selector。调用可以注册至 Selector 的 Channel 的 register() 方法可以完成这个任务(也就是说不是所有的 Channel 都可以注册到 Selector 中):

channel.configureBlocking(false);//设置为非阻塞的

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);//返回一个SelectionKey对象

 Selector 管理的 Channel 必须是非阻塞模式的,这意味着不能使用 Selector 处理 FileChannel,因为 FileChannel 不能切换到非阻塞模式,但 Socket 相关的 Channel 是可以的。

 注意 register() 方法的第二个参数,这是一个“关注点的集合”,就是 Selector 需要监听注册的 Channel 的什么事件。有以下四个不同的事件类型可以监听:
  1️⃣Connect
  2️⃣Accept
  3️⃣Read
  4️⃣Write
 一个 Channel “触发一个事件”通常也叫做为某个事件做好准备。因此,一个 Channel 成功连接到了另一个服务器就是一个 Connect 事件,一个服务器的 SocketChannel 接收到一个进来的连接就是一个 Accept 事件,一个 Channel 有准备好的数据需要读取就是一个 Read 事件,一个 Channel 准备好使你可以向其写入数据就是一个 Write 事件。这四个事件由 SelectionKey 的四个常量表示:
  1️⃣SelectionKey.OP_CONNECT
  2️⃣SelectionKey.OP_ACCEPT
  3️⃣SelectionKey.OP_READ
  4️⃣SelectionKey.OP_WRITE
 如果关注的事件不止一个,可以通过这些常量的或运算实现,如下所示:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

 4、SelectionKey
 当将一个 Channel 注册到一个 Selector 时,register() 方法会返回一个 SelectionKey 对象,这个SelectionKey 对象有一些需要关注的属性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

  ①Interest Set
  Interest Set 是“选择”感兴趣的事件集,如“向 Selector 中注册 Channel”中所述。 您可以通过 SelectionKey 读取和写入该兴趣集,如下所示:

int interestSet = selectionKey.interestOps();//获取SelectKey的关注集

//使用与运算判断是不是指定的关注事件
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE; 

  如上面的代码所示,可以通过使用关注集和给定的关注事件作“与运算”来判断关注集中是否包含该关注事件。
  ②Ready Set
  Ready Set(就绪集) 是 Channel 已经准备好了的一组操作的集合。“选择”后的主要工作就是访问这个 Ready Set(“选择”将在后面讲述),可以按以下方式访问就绪集:

int readySet = selectionKey.readyOps();

  可以采用像 InterestSet 中的作“与运算”的方式判断哪些事件或操作已就绪,也可以调用如下所示的四个方法,这些方法都返回布尔值:

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

  ③Channel + Selector
  通过 SelectionKey 访问 channel 和 selector 很简单:

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

  ④Attaching Objects
  你可以将一个对象附加到一个 SelectionKey,这是识别给定通道或将更多信息附加到该通道的便捷方法。 例如,你可以将正在使用的 Buffer 附加到这个 Channel,或者附加一个包含更多聚合数据的对象。 下面是附加对象的方式:

selectionKey.attach(theObject);//附加对象

Object attachedObj = selectionKey.attachment();//获取附加对象

  也可以在向 Selector 注册的时候往 Channel 中附加对象:

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

 5、通过 Selector 选择 Channel
 使用选择器注册一个或多个 Channel 后,您可以调用其中的一个 select() 获取 Channel,这些方法将返回“关注事件”(连接,接受,读取或写入)的“就绪” Channel。 换句话说,如果你关注读取,select() 方法就会返回读取就绪的 Channel。下面是几个 select() 方法:

  • int select()
  • int select(long timeout)
  • int selectNow()

  ①select() 方法会一直阻塞直到至少一个你所关注的事件的 Channel 准备就绪
  ②select(long timeout) 方法会一直阻塞直到至少一个你所关注的事件的 Channel 准备就绪或者达到超时时间
  ③selectNow() 方法是非阻塞的,无论是否有 Channel 就绪都会立即返回
 select() 方法的返回值是就绪的 Channel 的个数,注意这个数值是从你上一次调用 select() 到这次调用之间就绪的 Channel 的个数。如果你调用 select() 方法返回的是 1 说明从你上次调用 select() 到现在为止有一个 Channel 已就绪,也就是说 select() 方法返回的是在这期间“变为就绪”的 Channel 的个数,而不是所有的“已就绪”的 Channel 的个数。
 一旦调用了其中的一个 select() 方法,并且其返回值显示有一个或多个 Channel 已就绪,则可以通过调用选择器的 selectedKeys() 方法获取到“ selected key”的集合,然后通过“selected key”访问就绪的 Channel。如下所示:

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

 在向 Selector 中注册 Channel 的时候 Channel.register() 方法会返回一个 SelectionKey 对象。这个 SelectionKey 表示这个 Channel 注册到了这个 Selector,可以通过 selector.selectedKeys() 访问到这些 SelectionKey 的集合,可以通过迭代这些 selected key 访问到就绪的 Channel:

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

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {
    
    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

 这样循环迭代“selected key”集合中的 key,判断其中的每一个 key 关联的 Channel 是为哪种事件完成了就绪。
 注意在每次迭代的最后都调用了 keyIterator.remove() 方法,Selector 不会主动的删除“selected key”集合中的 SelectionKey 实例,必须在我们处理完这个 Channel 后手动删除。下次这个 Channel “就绪”时 Selector 会再将其添加到 “selected key” 集合中。
 通过 SelectionKey.channel() 方法返回的 Channel 应该转换为相应类型的 Channel,比如转为 ServerSocketChannel 或者 SocketChannel 等
 6、wakeUp()
 一个线程在调用了 select() 后可能会阻塞(在没有就绪的 Channel 时),也可以不用等到有 Channel 就绪就使 select() 方法返回。只需要在另一个线程中通过调用同一个 Selector 对象的 wakeup() 方法,就会使该 Selector 对象的阻塞着的 select() 方法立即返回,注意是同一个 Selector 对象。
 如果其他线程调用某个 Selector 的 wakeup() 方法,而该 Selector 对象没有因调用 select() 阻塞的线程,则下一个调用该 Selector 的 select() 方法的线程将会被立即“唤醒”。
 7、close()
 在结束使用 Selector 后记得调用其 close() 方法将其 close。这将关闭该选择器,并使在此选择器中注册的所有 SelectionKey 实例失效。 但 Channel 本身并不会被关闭。
 8、完整示例
 下面是一个完整的示例,包括 Selector 的创建,Channel 的注册,事件的监听等:

Selector selector = Selector.open();

channel.configureBlocking(false);

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

while(true) {
  int readyChannels = selector.selectNow();

  if(readyChannels == 0) continue;


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

  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值