目录

NIO(一、概述)

NIO(二、Buffer)

NIO(三、Channel)

NIO(四、Selector)

Selector

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

  

如何使用

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

  • 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;

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

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

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

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

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

selectionKey.attach(ch_name);
  • Block

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

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

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

selector.select(5000);

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

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();

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. c++ 继承类强制转换时的虚函数表工作原理

    本文通过简单例子说明子类之间发生强制转换时虚函数如何调用,旨在对c++继承中的虚函数表的作用机制有更深入的理解. #include<iostream> using namespace st ...

  2. iOS开发常用

    http://blog.csdn.net/u013043666/article/details/51353386 1.打电话 第一种 NSString *telNum = model.contact; ...

  3. 提升iOS审核通过率之“IPv6兼容测试”

    作者:jingle 腾讯系统测试工程师 商业转载请联系腾讯WeTest授权,非商业转载请注明出处. 原文链接:http://wetest.qq.com/lab/view/285.html 一.背景 在 ...

  4. MVC中登录页图片验证码总结

    直接上代码了 using System;using System.Collections.Generic;using System.Drawing;using System.Drawing.Imagi ...

  5. api接口json串换行

    1.问题描述:在后台输入框中明明回车换行了,但是返回到 app客户端显示出来的 确实带有 \n  这个时候无论怎么调试都不行: 2.铺垫:大家都知道 php输出字符串的时候  使用 单引号 比使用 双 ...

  6. Unity渲染优化中文翻译(二)——CPU的优化策略

    紧接上一篇文章,继续渲染的优化问题,若有错误,请指出,让我也学习进步,谢谢. 如果游戏渲染问题来自CPU 概括的来说,CPU在一帧的渲染中的工作可以分为三个部分: . 决定谁需要被渲染 . 为GPU准 ...

  7. 【异构计算】在Windows下使用OpenCL配置

    前言 目前,NVIDIA 和 AMD 的 Windows driver 均有支持OpenCL(NVIDIA 的正式版 driver 是从自195.62 版开始,而 AMD则是从9.11 版开始).NV ...

  8. PHP基础学习(函数一)

    PHP(Hypertext Preprocessor):超文本预处理器,一种嵌入在HTML中并且运行在服务器端的脚本语言. var_dump--打印变量相关信息 说明:  <?php var_d ...

  9. 【PHP系列】PHP推荐标准之PSR-1,PSR-2

    说起码代码,刚上大学那会,老师就教导我们,要严格,规范的,把代码写好.代码如人,工工整整.提起规范化的代码,从一开始用命令行编辑C语言代码就开始控制,强制自己按照相关的标准来,所以,现在写代码,不规范 ...

  10. 由于java.util.Arrays.asList(...)导致的异常

    前言: Collections.toArray()与Arrays.asList() 是Java API提供的友好的相互转换工具,日常开发中用于列表和数组之间的转换非常方便,但今天测试时,发现一下隐藏的 ...