目录

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. css3 3d初入门(一)

    css3 3D.html div.oembedall-githubrepos { border: 1px solid #DDD; list-style-type: none; margin: 0 0 ...

  2. [数据共享(干货)] wcf rest 服务发布数据 以及获得数据

    最近在做一个项目 ,需要我们做一个东西,我们姑且叫数据共享吧,我们公司叫A公司,对方公司叫B公司,就是A公司提供一个数据服务接口出去,B公司如果想拿我们数据的时候直接调用我们的服务接口就行了,我们同样 ...

  3. Spring中LocalSessionFactoryBean与SessionFactory

    相信不少人多纠结LocalSessionFactoryBean与SessionFactory到底是什么关系,怎么去进行关联的,正如图所示: transactonManager有一个对sessionFa ...

  4. swift 运算符快速学习(建议懂OC或者C语言的伙伴学习参考)

    昨晚看了swift 的运算符的知识点,先大概说一下,这个点和 c 或者oc 的算运符知识点一样,都是最基础最基础的.其他的最基本的加减乘除就不多说了.注意的有几点点..先说求余数运算: 一 :求余数运 ...

  5. Android注解学习(2)

    最近考试周没什么时间写,回归正题.前面的一次简单的讲了关于注解的的基础部分,这一次分析xutils注解封装的源码(奉上github源码). 补充下:xUtils 2.x对Android 6.0兼容不是 ...

  6. php单例模式与工厂模式

    单例模式:单例模式又称为职责模式,它用来在程序中创建一个单一功能的访问点,通俗地说就是实例化出来的对象是唯一的. 所有的单例模式至少拥有以下三种公共元素:1. 它们必须拥有一个构造函数,并且必须被标记 ...

  7. shell-早间学习,每日一点

    1.定位行: sed -n '12,~3p' pass #从第12行开始,直到下一个3的倍数行(12-15行) sed -n '12,+4p' pass #从第12行开始,连续4行(12-16行) s ...

  8. github fork, star and watch

    1 git fork git clone原版本的话,只有读权限,是不能直接把修改提交到服务器的. git fork会创建一个副本,然后就可以在这个上面进行开发了,开发了之后可以通过pull reque ...

  9. c#之循环效率

    很多人在保存数据时候对于使用数组.集合等?然后遍历数据的时候是for.froeach? 下面我就写一个小例子进行测试看看,话不多说,直接用数据说话. 1.构建数据分别是数组.集合构建,数据类型分别是值 ...

  10. jQ试题的总结

    jQuery习题的一些总结   1.在div元素中,包含了一个<span>元素,通过has选择器获取<div>元素中的<span>元素的语法是? 提示使用has $ ...