NIO(四、Selector)
目录
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)的更多相关文章
- java学习-NIO(四)Selector
这一节我们将探索选择器(selectors).选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能.就像在第一章中描述的那样,就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/ ...
- Java NIO类库Selector机制解析(上)
一. 前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式.NIO的包中主要包含了这样几种抽象数据类型: ...
- NIO组件Selector工作机制详解(上)
转自:http://blog.csdn.net/haoel/article/details/2224055 一. 前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但 ...
- Java NIO类库Selector机制解析--转
一. 前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式.NIO的包中主要包含了这样几种抽象数据类型: ...
- Java NIO之Selector(选择器)
历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂re ...
- Nio使用Selector客户端与服务器的通信
使用NIO的一个最大优势就是客户端于服务器自己的不再是阻塞式的,也就意味着服务器无需通过为每个客户端的链接而开启一个线程.而是通过一个叫Selector的轮循器来不断的检测那个Channel有消息处理 ...
- Java NIO类库Selector机制解析(下)
五. 迷惑不解 : 为什么要自己消耗资源? 令人不解的是为什么我们的Java的New I/O要设计成这个样子?如果说老的I/O不能多路复用,如下图所示,要开N多的线程去挨个侦听每一个Channel ...
- NIO的Selector
参考自 Java NIO系列教程(六) Selector Java-NIO-Selector java.nio.channels.Selector NIO新功能Top 10(下) 出发点: 如何管理多 ...
- Java NIO 选择器(Selector)的内部实现(poll epoll)
http://blog.csdn.net/hsuxu/article/details/9876983 之前强调这么多关于linux内核的poll及epoll,无非是想让大家先有个认识: Java NI ...
- NIO组件Selector调用实例
*对于nio的非阻塞I/O操作,使用Selector获取哪些I/O准备就绪,注册的SelectionKey集合记录关联的Channel这些信息.SelectionKey记录Channel对buffer ...
随机推荐
- 利用canvas制作乱跑的小球
canvas制作乱跑的小球 说明:将下面的代码放到html的body就可以,键盘控制上(W)下(S)左(A)右(D) <body> <canvas id="canvas&q ...
- java基础知识点---equal,==,hashcode
1.==比较对象之间的地址是否相同 student a=new student(1); student b=new student(1); a==b false b=a; a==b true ...
- 隐藏Apache的版本号及其它敏感信息
首先,打开配置文件: /etc/apache2/apache2.conf 来修改ServerTokens 的配置: 找到:ServerTokens Full 所在行,将其改为: ServerToken ...
- Ninject之旅之十二:Ninject在Windows Form程序上的应用(附程序下载)
摘要: 下面的几篇文章介绍如何使用Ninject创建不同类型的应用系统.包括: Windows Form应用系统 ASP.NET MVC应用系统 ASP.NET Web Form应用系统 尽管对于不同 ...
- (转)JAVA的整型与字符串相互转换
JAVA的整型与字符串相互转换1如何将字串 String 转换成整数 int? A. 有两个方法: 1). int i = Integer.parseInt([String]); 或 ...
- 如何利用fis3来模拟后台返回数据
Node 版本要求 0.8.x,0.10.x, 0.12.x,4.x,6.x,不在此列表中的版本不予支持.最新版本 node 支持会第一时间跟进,支持后更新支持列表 1.安装fis3 npm inst ...
- web前端简介
Web标准: 结构(硬件):xhtml html 表现(软件):css 行为(插件):dom js html:超文本标记语言 (Hyper Text Markup Language) xhtml:可 ...
- PHPCMS-后台管理中心
这个就是便捷管理网页,可以通过这个后台进行修改.增删一些东西,还可以利用一些网页模板来建立网页 首先就是下载好这个后台管理中心,这个从网上下载就好了,记住这个要安装在WampServer中的www文件 ...
- 不惧面试:HTTP协议(3) - Cookie
v博客前言 先交代下背景,写这个系列的原因是总结自己遇到的面试题以及可能遇到的题目,更重要的是,今年定的目标是掌握网络这一块的知识点,先是搞懂HTTP协议,然后是TCP/IP协议,再就是WCF如何运用 ...
- Scala入门 【1】
Scala入门 [1] 转载请注明出处:http://www.cnblogs.com/BYRans/ 1 基础 val定义的为常量,var为变量 val name:Type = ***,变量名后加冒号 ...