目录

NIO(一、概述)

NIO(二、Buffer)

NIO(三、Channel)

NIO(四、Selector)

Selector

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

  

如何使用

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

  • Create

      只需要调用 open() 即可创建一个Selector对象:
  1. Selector selector = Selector.open();
  • Register

      通过 register() 方法注册通道:
  1. ServerSocketChannel channel = ServerSocketChannel.open();
  2. channel.configureBlocking(false);
  3. SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);

  在注册通道之前,把通道设置成非阻塞模式,观察源码会发现 register() 会校验当前通道是否为非阻塞模式,当是阻塞模式时,会抛出IllegalBlockingModeException 异常。在前面一个章节也提过,为什么FileChannel没有继承SelectableChannel,因为它不需要多路复用,所以在使用通道的时候,只有FileChannel不能向选择器注册通道,凡是继承SelectableChannel都能够向选择器注册通道。

  注册通道方法的第二个参数是SelectionKey中定义的操作类型,你可以填入任何你感兴趣的操作类型,只要这个通道支持,同样,在执行 register() 方法时也会校验该通道是否能够支持该操作。

  注册方法同样也会返回一个SelectionKey对象。

  • Attach Object

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

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

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

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

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

  当然,注册时返回的SelectionKey对象也可以在使用时候附加你想要的附加对象:

  1. selectionKey.attach(ch_name);
  • Block

      因为是一个线程通过选择器来操作通道,那么选择器在操作通道时,必定在处理一个通道的时候,另一个事件已就绪的通道处于等待状态。在确定一个通道事件就绪之后,才能去操作这个通道。上文中讲到使用注册方法register使用的代码示例,将ServerSocketChannel对象向选择器注册,同时关注了这个通道的OP_ACCEPT操作类型事件,那么我们什么时候能确定该通道的accept事件就绪,可以操作这个通道了。选择器为我们提供了三个重载的 select() 方法,这三个方法的主要功能就是选择是否阻塞直到该选择器中的通道所关注的事件就绪,来让程序继续往下运行。

      首先看 select() 方法,该方法会一直阻塞下去,直到选择器中的通道关注的事件就绪:
  1. selector.select();

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

  1. selector.select(5000);

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

  1. selector.selectNow();

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

  • Keys & SelectionKeys

      我们每为一个通道执行 register() 注册方法,就会返回一个SelectionKey,那么这个选择器所有已就绪的SelectionKey就是通过selectedKeys()来获取:
  1. Set<SelectionKey> selectionKeys = selector.selectedKeys();

  一般这个方法是在select() 之后执行,因为到这一步就意味着要通过这个轮询每个就绪的通道。

  1. Iterator<SelectionKey> iterator = selectionKeys.iterator();
  2. while (iterator.hasNext()) {
  3. SelectionKey key = iterator.next();
  4. if (key.isAcceptable()) {
  5. // 执行通道的操作
  6. }
  7. //执行完成移除
  8. iterator.remove();
  9. }

  到这里说的是已就绪的通道,那么所有的 SelectionKey 集可以通过 keys() 方法获取:

  1. 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这几种操作:

  1. // SocketChannel类
  2. public final int validOps() {
  3. return (SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT);
  4. }
  • interestOps & readyOps

      这是SelectionKey的实现类中定义的变量:
  1. private volatile int interestOps;
  2. private int readyOps;

  interestOps用来存储感兴趣的操作集,readyOps用来存储已经就绪的操作集。

  其中 interestOps() 方法和 nioInterestOps() 都会返回interestOps,不同的是interestOps()会校验是否已执行 cancel() ,如果已经取消则会抛出 CancelledKeyException 异常。readyOps同样也有 readyOps() 和 nioReadyOps() 方法,逻辑与interestOps几乎一致。

  观察这段SelectionKey抽象类已经实现的代码:

  1. // SelectionKey 类
  2. public final boolean isAcceptable() {
  3. return (readyOps() & OP_ACCEPT) != 0;
  4. }

  当判断是否访问就绪的时候,只要 readyOps() 与相应的操作类型相与,非零就返回true,代表接受请求操作已就绪。这个是SelectionKey已提供的方法,但是SelectionKey并未提供同样返回boolean判断某个操作在interestOps集是否存在,我们可以自己实现这些方法:

  1. private boolean isInterestRead(SelectionKey selectionKey){
  2. return (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_READ;
  3. }
  4. private boolean isInterestWrite(SelectionKey selectionKey){
  5. return (selectionKey.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
  6. }
  7. private boolean isInterestConnect(SelectionKey selectionKey){
  8. return (selectionKey.interestOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
  9. }
  10. private boolean isInterestAccept(SelectionKey selectionKey){
  11. return (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
  12. }
  • Channel、Selector

      在SelectionKey中获取通道或者选择器只需要调用其中的两个方法即可:
  1. SelectableChannel selectableChannel = selectionKey.channel();
  2. Selector sel = selectionKey.selector();

NIO(四、Selector)的更多相关文章

  1. java学习-NIO(四)Selector

    这一节我们将探索选择器(selectors).选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能.就像在第一章中描述的那样,就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/ ...

  2. Java NIO类库Selector机制解析(上)

    一.  前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式.NIO的包中主要包含了这样几种抽象数据类型: ...

  3. NIO组件Selector工作机制详解(上)

    转自:http://blog.csdn.net/haoel/article/details/2224055 一.  前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但 ...

  4. Java NIO类库Selector机制解析--转

    一.  前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式.NIO的包中主要包含了这样几种抽象数据类型: ...

  5. Java NIO之Selector(选择器)

    历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂re ...

  6. Nio使用Selector客户端与服务器的通信

    使用NIO的一个最大优势就是客户端于服务器自己的不再是阻塞式的,也就意味着服务器无需通过为每个客户端的链接而开启一个线程.而是通过一个叫Selector的轮循器来不断的检测那个Channel有消息处理 ...

  7. Java NIO类库Selector机制解析(下)

    五.  迷惑不解 : 为什么要自己消耗资源? 令人不解的是为什么我们的Java的New I/O要设计成这个样子?如果说老的I/O不能多路复用,如下图所示,要开N多的线程去挨个侦听每一个Channel ...

  8. NIO的Selector

    参考自 Java NIO系列教程(六) Selector Java-NIO-Selector java.nio.channels.Selector NIO新功能Top 10(下) 出发点: 如何管理多 ...

  9. Java NIO 选择器(Selector)的内部实现(poll epoll)

    http://blog.csdn.net/hsuxu/article/details/9876983 之前强调这么多关于linux内核的poll及epoll,无非是想让大家先有个认识: Java NI ...

  10. NIO组件Selector调用实例

    *对于nio的非阻塞I/O操作,使用Selector获取哪些I/O准备就绪,注册的SelectionKey集合记录关联的Channel这些信息.SelectionKey记录Channel对buffer ...

随机推荐

  1. java 继承的学习(转)

    转自:http://www.cnblogs.com/happyframework/p/3332243.html,非常感谢啊 public class test { /** * @param args ...

  2. object c入门

    无意间看到Object C编写的程序,感觉蛮有意思的,记载下来,慢慢品味,也许会有用得上的时候.吼吼~~ 大部分有一点其他平台开发基础的初学者看到XCode,第一感想是磨拳擦掌,看到 Interfac ...

  3. JSP 学习二

    在基于昨天对JSP学习的基础上,今天我们来学习JSP的指令和JSP 对中文的处理. 一.JSP指令简介 JSP 指令是为JSP引擎而设计,它并不直接产生任何可见的输出,而只是告诉引擎如何处理JSP页面 ...

  4. JAVA设计模式:装饰模式

    前面我们学习了代理模式: 代理模式主要使用了java的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,大家知根知底,你 ...

  5. WP8.1开发中关于媒体(图片)文件的生成操作,属性如何设置(内容/嵌入资源等);

    (转载)WindowsPhone问题笔记-- 正确选择build action 解决媒体资源无法读取问题 链接:http://www.cnblogs.com/qinxg/archive/2012/07 ...

  6. nginx.conf完整配置实例

        #user nobody; worker_processes 1;   #error_log logs/error.log; #error_log logs/error.log notice; ...

  7. django-查询语句(一)

    1.model 假设我们的model如下: 某个JobType下有很多Job. class JobType(models.Model): name = models.CharField(max_len ...

  8. C#之系统异常处理机制

    在系统开发过程中,BUG和异常产生是无处不在的,但是需要我们去做的就是不断去发掘异常.修改异常. 这篇文章主要谈谈我在系统中解决异常的几种方法: 1.控制台程序产生的异常: 在大多数的控制台程序中,运 ...

  9. JAVA包名、类名、变量名命名规则

    类名:首字母大写,其他单词中首字母大写,其他小写; 方法名:首字母小写,其他单词中首字母大写,其他小写: 变量:首字母小写,其他单词中首字母大写,其他小写: 包名:全部小写

  10. jvm系列(八):jvm知识点总览-高级Java工程师面试必备

    在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功.对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来取胜了,但是练到高手之后 ...