前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java NIO-6.Selector

Java NIO-6.Selector

作者头像
悠扬前奏
发布2019-06-02 12:55:31
4150
发布2019-06-02 12:55:31
举报

Selector是Java NIO中的一个组件,用于检查一个或者多个NIO Channel,并确定哪一个Channel已经准备好读或者写了。 这样一个进程能管理多个通道,也意味着多个网络连接。

优点

Selector的优势在于一个进程能够处理多个通道,这样可以用更少的进程来控制通道了。事实上,可以只用一个线程处理所有的通道。在操作系统中切换进程开销是很大的,并且每个线程也需要占用一定的系统资源。因此,线程的使用越少越好。 要记住,现代操作系统和CPU在多任务处理方面表现越来越好,所以多线程的开销也变得越来越小。事实上,如果CPU有多个核心,不使用多任务是浪费了CPU的能力。不管怎样,关于设计的讨论属于不同的文章了。在这里,只需要知道Selector能够用一个进程管理多个通道就可以了。 下面是用一个Selector处理三个Channel的示例图:

A Selector to handle 3 Channels

创建Selector

通过调用Selector.open方法,创建一个Selector:

Selector selector = Selector.open();

用Selector注册Channels

为了将Channel和Selector配合使用,需要用Selector注册Channel。通过Selector.register()方法来实现:

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

和Selector一起使用时,Channel必须属于非阻塞模式。这意味着FileChannel不能喝Selector一起使用,因为FileChannel不能切换为非阻塞模式。套接字(Socket)的通路可以。

注意register()方法的第二个参数,这是个“兴趣集合”,意思是通过Selector监听Channel时,对哪些事件感兴趣。有四种事件可以监听:

  1. Connect
  2. Accept
  3. Read
  4. Write 通道出发了一个事件也被称为该事件“就绪”。所以,一个通路和另一个服务连接成功就是“连接就绪”。接受了传入连接的服务套接字通道就是“接受”就绪。数据准备好被读取的通道就“读取”就绪,同样还有“写入”就绪。 这四个事件代表了四个SelectionKey常量:
  5. SelectionKey.OP_CONNECT
  6. SelectionKey.OP_ACCEPT
  7. SelectionKey.OP_READ
  8. SelectionKey.OP_WRITE

如果对多个事件有“兴趣”,将常量用"或"连接:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;    

在文章后面还会提到兴趣结合。

SelectionKey

正如前文中,想Selector中注册Channel时,register()方法返回了一个SelectionKey对象。包括一些有趣的属性:

  • interest集合
  • ready集合
  • Channel
  • Selector
  • 附加对象(可选) 下面讲述这些属性
Interest 集合

interest集合是在“Selecting”中感兴趣的事件集合。可以通过SelectionKey读写interest集合,例如:

int interestSet = selectionKey.interestOps();

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

可以看到,用给定的SelectionKey常量对interest集合进行操作来确定事件是否在interest集合中。

Ready 集合

Ready集合是通道已经就绪的操作的集合。在selection之后,将首先访问这个集合。selection将会在下一小节解释。 可以这样访问ready集合:

int readySet = selectionKey.readyOps();

能用检测interest集合同样的方式检测通道中就绪的事件或操作。但是也可以使用下面四种方法,都返回布尔值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel +Selector

从SelectionKey中访问Channel+selector很简单,如下:

Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
附加对象

能在SelectionKey上附加一个对象,用来识别给定的通道,或者附上Channel更多的信息。例如,可以附上和通道一起使用的Buffer,或者包含更多聚合对象的对象。使用方法如下:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

还可以在用register()方法向Selector注册Channel的时候附上一个对象:

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

通过Selector选择通道

一旦向Selector注册了一个或多个通道,就能调用selector()方法中的一个。这些方法返回你感兴趣的事件(连接,接收,读,写)已经就绪的那些通道,换句话说,如果对读就绪的通道感兴趣,select()方法会返回准备好进行读取的通道。 以下是select()方法:

  • int select()
  • int select(long timeout)
  • int selectNow() select()方法会阻塞,直到至少有一个通道在你注册的事件上就绪了。 select(long timeout)和select()方法一样。除了它最多只阻塞timeout毫秒。 selectNow()完全不阻塞,无论通通道是否就绪都立即返回。 select()方法返回的int是有多少通道就绪了。也就是自上一次调用select()之后有多少通道就绪。如果调用select()返回1,就是说有一个通道就绪了,如果再次调用一个select()方法,又有一个通道就绪了,就会再次返回1。如果在第一个通道就绪之后没有调用,现在就有两个就绪的通道了。但是两次调用select()方法之间只有一个通道就绪。

selectedKeys()

一旦调用了一个select()方法,返回的值表明有一个或者多个通道就绪了,就能够调用选择器selectedKeys()方法通过“选择的key集合”访问就绪的集合:

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

用Selector注册通路时,Channels.register()方法会返回一个SelectionKey对象。这个键代表用该Selector注册的证书。也就SelectionKey中通过selectedKeySet()访问的键。 对选择的键集合进行迭代来访问就绪的通道,例如:

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

    if(key.isAccepted()) {
        // 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();
}

这个循环对选择的键集合进行迭代,确认每个键代表的通道是否就绪。

注意每次迭代最后调用了keyIterator.remove()方法。Selector不会自动移除从键集合中SelectionKey实例。需要再处理完通道之后显式移除。下次通道就绪后Selector会把它重新加入键集合中。 需要把SelectionKey.channel()方法返回的通道转换为要处理的类型,例如ServerSocketChannel或者SocketChannel等。

wakeUp()

一个线程调用select()方法阻塞后,即使没有通道就绪,也能从select()方法返回。只要让其他线程在第一个调用select()方法的线程上的对象上调用Selector.wake()方法。在select()方法上等待的线程会立即返回。 如果另外的线程调用wakeup()但是没有线程阻塞在select()方法,下一个调用select()方法的线程会立即“wake up”。

完整的Selector范例

下面是一个打开Selector,用它注册通道(省略通道实例),然后监视Selector的四个事件(接受,连接,读,写)进行监听。

Selector selector = Selector.open();

channel.configureBlocking(false);

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


while(true) {

  int readyChannels = selector.select();

  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();
  }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.06.04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 优点
  • 创建Selector
  • 用Selector注册Channels
  • SelectionKey
    • Interest 集合
      • Ready 集合
        • Channel +Selector
          • 附加对象
          • 通过Selector选择通道
          • selectedKeys()
          • wakeUp()
          • 完整的Selector范例
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档