Java Socket:Java-NIO-Selector
Selector 的出现,大大改善了多个 Java Socket的效率。在没有NIO的时候,轮询多个socket是通过read阻塞来完成,即使是非阻塞模式,我们在轮询socket是否就绪的时候依然需要使用系统调用。而Selector的出现,把就绪选择交给了操作系统(我们熟知的selec函数),把就绪判断和读取数据分开,不仅性能上大有改善,而且使得代码上更加清晰。
Java NIO的选择器部分,实际上有三个重要的类。
1,Selector 选择器,完成主要的选择功能。select(), 并保存有注册到他上面的通道集合。
2,SelectableChannel 可被注册到Selector上的通道。
3,SelectionKey 描述一个Selector和SelectableChannel的关系。并保存有通道所关心的操作。
接下来,便是一个通用的流程。
首先, 创建选择器,
然后,注册通道,
其次,选择就绪通道,
最后,处理已就绪通道数据。
让我们通过代码来看这些步骤是如何完成的。
- Selector selector = Selector.open();
- channel1.configureBlocking(false);
- channel2.configureBlocking(false);
- cahnnel3.configureBlocking(false);
- SelectionKey key1 = channel1.register(selector, SelectionKey.OP_READ);
- SelectionKey key2 = channel2.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
- SelectionKey key3 = channel3.register(selector, SelectionKey.OP_WRITE);
- while(true){
- );
- ) continue;
- Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
- while(iter.hasNext()){
- SelectionKey key = iter.next();
- if( key.isReadable()){
- readData(key);
- }
- iter.remove();
- }
- }
上面的代码是一个示例。我们可以看到,创建一个Selector使用open方法,这是一个静态工厂模式,注意他的异常处理是IOException。接下来的通道,我们并没有说明是什么通道,一般来说,基本上Socket类通道是可选择的,但是文件类的是不可选择的。
我们可以看到的是,这个通道调用了 configureBlocking(false)这样的方法,在注册到Selector上之前,通道应该保证是非阻塞的,否则异常IllegalBlockingModeException抛出。
之后我们开始注册通道,使用registor方法,主意后面一个参数,如果对一个只读的通道注册写操作,是会抛出异常IllegalArgumentException的。例如SocketChannel不支持accept操作。这里一共有四种操作 read,write,accept,connect。
当然,我们还不能把已经关闭的通道注册到Selector中,而Selector如果调用close,那么试图访问它的大多数操作都会抛出异常。
接下来,我们开始使用select函数更新selectedKey,这里比较复杂,但是从代码看,我们做完select以后,就开始便利selectedKey,找到符合要求的key,进行读数据操作。这里还要注意的是,使用完key以后,需要从selectedKey集合中删除。
下面我们还有更详细的说明,因为我们还不知道这个select到底做了说明,selectedKey又是如何更新的呢?
首先,一个selectionKey 包含了两个集合,一个是 注册的感兴趣的操作集合,一个是已经准备好的集合。第一个集合基本上是注册就确定的,或者通过interestOps(int)来改变。select是不会改变interest集合的。但是select改变的是 ready集合。也就是准备好的感兴趣的操作的集合,这样说,也说明,ready集合实际上是interest集合的子集。
如何使用这些集合呢?
看代码:
- )
- {
- myBuffer.clear();
- key.channel().read(myBuffer);
- doSomething(myBuffer.flip());
- }
从上面的代码看出,这个集合只是一个掩码,需要和操作与,才能得到结果。
当然,也有更方便的用法。
- if ( key.isReadable() )
还要注意的是,这样的判断并不是就是一定的,只是一个提示。底层通道随时在改变。
对于SelectionKey, 还可以执行cancel操作,一个被cancel掉的SelectionKey,实际上只是被放到了Selector的cancel键集合里,键马上失效,但是通道依然是注册状态,要等到下一个select时才真正取消注册。
现在,我们再来看看选择器做了什么。选择器是就绪选择的核心,它包含了注册到它上面的通道与操作关系的Key,它维护了三个集合。
1,已经注册的键集合 调用, keys()
2,已经选择的键集合 调用, selectedKeys()
3,已经取消的键集合 私有。
选择器虽然封装了select,poll等底层的系统调用,但是她有自己的一套来管理这些键。
每当select被调用时,她做如下检查:
1,检查已经取消的键的集合。如果非空,从其他两个集合中移除已经取消的键,注销相关通道,清空已经取消的键的集合。
2,已注册的键的集合中的键的interest集合被检查。例如有新的interest的操作注册。但是这一步不会影响后面的操作。这是延时到下一次select调用时才会影响的。
就绪条件确认后,底层系统进行查询。依赖于select方法的参数,如果没有通道准备好,根select带的参数超时设置,可能会阻塞线程。
系统调用完成后,可以对操作系统指示的已经准备好的interest集合中的一种操作的通道,执行以下操作:
a: 如果通道的键还没有在已经选择的键的集合中,那么键的ready集合将被清空。然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。
b: 否则,一旦通道的键被放入已经选择的键的集合中时,ready集合不会被清除,而是累积。这就是说,如果之前的状态是ready的操作,本次已经不是ready了,但是他的bit位依然表示是ready,不会被清除。
3, 步骤2可能会有很长一段时间的休眠。所以在步骤2完成以后,步骤1继续执行以确保被取消的键正确处理。
4,返回值,select的返回值说明的是从上一次调用到本次调用,就绪选择的个数。如果上一次就已经是就绪的,那么本次不统计。这是是为何返回为0时,我们continue的原因。
这里使用的延迟注销方法,正是为了解决注销键的问题。如果线程在取消键的同时进行通道注销,那么很可能阻塞并与正在进行的选择操作发生冲突。
同样我们有3中select可以选择:
1, select()
2, select(long timeout)
3, selectNow();
select()会阻塞线程知道又一个通道就绪。
而select带timeout的会在特定时间内阻塞,或者至少有一个通道就绪。
而selectNow()如果没有发现就绪,就直接返回。
如何停止中断选择呢?
有三种方法。
1, wakeup()这是一种优雅的方法,同时也是延时的。如果当前没有正进行的选择操作,也就是要等到下一个select才起作用。
2, close()选择器的close被调用,则所有在选择操作中阻塞的线程被唤醒,相关通道被注销,键也被取消。
3, interrupt() 实际上interrupt并不会中断线程。而是设置线程中断标志。
然后依然是调用wakeup()。这是因为 Selector 捕获了interruptedException,然后在异常处理中调用了 wakeup()
根据以上的信息,我们可以了解到,实际上选择器对选择键中的集合的操作,是交给程序员来完成的。如何管理选择键,是很关键的。
这里需要记住的是,ready集合中的比特位,是累积的。根据步骤2,如果一个键是在选择集合中,那么这个键的ready集合是不会被清除的。而如果这个键不在选择集合中,那么就要首先清空这个键的ready集合,然后把就绪信息更新到这个ready集合上,最后,就是把这个键加入到已选择的集合中。
这也是为什么上面的流程中,我们为什么要把处理的键删除,因为如果不删除,下一次的信息是累积的,我们就不能分出本次select中那些操作就绪了。如果清除掉,那么下一次如果就绪,ready集合就是重置后更新的信息。
Java Socket:Java-NIO-Selector的更多相关文章
- Java Socket(3): NIO
NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候,程序也可以做其他事情, ...
- Java NIO——Selector机制源码分析---转
一直不明白pipe是如何唤醒selector的,所以又去看了jdk的源码(openjdk下载),整理了如下: 以Java nio自带demo : OperationServer.java Oper ...
- Java Socket NIO入门
Java Socket.SocketServer的读写.连接事件监听,都是阻塞式的.Java提供了另外一种非阻塞式读写.连接事件监听方式——NIO.本文简单的介绍一个NIO Socket入门例子,原理 ...
- Java NIO 的前生今世 之四 NIO Selector 详解
Selector Selector 允许一个单一的线程来操作多个 Channel. 如果我们的应用程序中使用了多个 Channel, 那么使用 Selector 很方便的实现这样的目的, 但是因为在一 ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- Java Socket IO(BIO、NIO)
总结下Java socket IO.首先是各种IO的定义,这个定义似乎也是众说纷纭.我按照stackoverflow上面的解释: IO有两种分法:按照阻塞或者按照同步.按照阻塞,有阻塞IO和非阻塞IO ...
- (四:NIO系列) Java NIO Selector
出处:Java NIO Selector 1.1. Selector入门 1.1.1. Selector的和Channel的关系 Java NIO的核心组件包括: (1)Channel(通道) (2) ...
- Java IO 和 NIO
昨天面试问到了有关Java NIO的问题,没有答上来.于是,在网上看到了一篇很有用的系列文章讲Java IO的,浅显易懂.后面的备注里有该系列文章的链接.内容不算很长,需要两个小时肯定看完了,将该系列 ...
- Java中的NIO基础知识
上一篇介绍了五种NIO模型,本篇将介绍Java中的NIO类库,为学习netty做好铺垫 Java NIO 由3个核心组成,分别是Channels,Buffers,Selectors.本文主要介绍着三个 ...
随机推荐
- Android开发学习之路--React-Native之初体验
近段时间业余在学node.js,租了个阿里云准备搭建后端,想用node.js,偶尔得知react-native可以在不同平台跑,js在iOS和android上都可以运行ok,今天就简单学习下rea ...
- Linux系统编程----孤儿进程
什么是孤儿进程? 孤儿进程, 指在父进程退出后,而子进程还在运行,这个子进程就成了孤儿进程,这时由init进程(pid=1)接管 来看看例子: #include <stdio.h> #i ...
- tomcat集群的failover机制
集群要提供高可用性就必须要有某种机制去保证,常用的机制为failover(故障转移),简单说就是通过一定的heartbeat检测是否有故障,一旦故障发生备份节点则接管故障节点的工作. tomcat使用 ...
- Hibernate配置文件current_session_context_class的意思
转自:http://shuaigg-babysky.iteye.com/blog/563423 此设置的作用如下: What does sessionFactory.getCurrentSession ...
- 百度的android面试总结分析
今天就是今天上午10点,我接到了百度的电话面试,当然提前和我说了,我的拖延症是有多强烈,以至于我没怎么准备,当然我也想着看看自己的真实水平,在此检讨一下!!!!!!!!!!!!!!!!!!!!!!!! ...
- Linux_Oracle命令大全
一,启动 1.#su - oracle 切换到oracle用户且切换到它的环境 2.$lsnrctl status 查看监听及数据库状态 3.$ls ...
- OC可点击的两种轮播图效果
基本上,每一个APP都有一个轮播图的效果展示,一般都是用来展示图片的一些信息,然后可以点击查看或购买,所以在此我将这种轮播图进行了一个类的封装,效果包含两种形式:第一种,来回轮转样式,第二种,一个方向 ...
- UNIX环境高级编程——存储映射I/O(mmap函数)
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共 ...
- nginx root、alias、location指令使用方法
一.nginx root指令 1. Nginx配置 相关配置如下图: 通过配置root目录到"/wwwroot/html/"位置 在用虚拟主机方法,主机名称是test,需要大家配置 ...
- AngularJS进阶(三十八)上拉加载问题解决方法
AngularJS上拉加载问题解决方法 项目中始终存在一个问题:当在搜索栏输入关键词后(见图1),按照既定的业务逻辑应该是服务端接收到请求后,首先返回查询的前7条数据,待客户端出现上拉加载时,继续查找 ...