Selector 的出现,大大改善了多个 Java Socket的效率。在没有NIO的时候,轮询多个socket是通过read阻塞来完成,即使是非阻塞模式,我们在轮询socket是否就绪的时候依然需要使用系统调用。而Selector的出现,把就绪选择交给了操作系统(我们熟知的selec函数),把就绪判断和读取数据分开,不仅性能上大有改善,而且使得代码上更加清晰。 



Java NIO的选择器部分,实际上有三个重要的类。 

1,Selector 选择器,完成主要的选择功能。select(), 并保存有注册到他上面的通道集合。 

2,SelectableChannel 可被注册到Selector上的通道。 

3,SelectionKey 描述一个Selector和SelectableChannel的关系。并保存有通道所关心的操作。 



接下来,便是一个通用的流程。 

首先, 创建选择器, 

然后,注册通道, 

其次,选择就绪通道, 

最后,处理已就绪通道数据。 



让我们通过代码来看这些步骤是如何完成的。

  1. Selector selector = Selector.open();
  2. channel1.configureBlocking(false);
  3. channel2.configureBlocking(false);
  4. cahnnel3.configureBlocking(false);
  5. SelectionKey key1 = channel1.register(selector, SelectionKey.OP_READ);
  6. SelectionKey key2 = channel2.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
  7. SelectionKey key3 = channel3.register(selector, SelectionKey.OP_WRITE);
  8. while(true){
  9. );
  10. ) continue;
  11. Iterator<SelectionKey> iter = selector.selectedKeys.iterator();
  12. while(iter.hasNext()){
  13. SelectionKey key = iter.next();
  14. if( key.isReadable()){
  15. readData(key);
  16. }
  17. iter.remove();
  18. }
  19. }

上面的代码是一个示例。我们可以看到,创建一个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集合的子集。 



如何使用这些集合呢? 

看代码:

  1. )
  2. {
  3. myBuffer.clear();
  4. key.channel().read(myBuffer);
  5. doSomething(myBuffer.flip());
  6. }

从上面的代码看出,这个集合只是一个掩码,需要和操作与,才能得到结果。 

当然,也有更方便的用法。

  1. 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的更多相关文章

  1. Java Socket(3): NIO

    NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候,程序也可以做其他事情, ...

  2. Java NIO——Selector机制源码分析---转

    一直不明白pipe是如何唤醒selector的,所以又去看了jdk的源码(openjdk下载),整理了如下: 以Java nio自带demo : OperationServer.java   Oper ...

  3. Java Socket NIO入门

    Java Socket.SocketServer的读写.连接事件监听,都是阻塞式的.Java提供了另外一种非阻塞式读写.连接事件监听方式——NIO.本文简单的介绍一个NIO Socket入门例子,原理 ...

  4. Java NIO 的前生今世 之四 NIO Selector 详解

    Selector Selector 允许一个单一的线程来操作多个 Channel. 如果我们的应用程序中使用了多个 Channel, 那么使用 Selector 很方便的实现这样的目的, 但是因为在一 ...

  5. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

  6. Java Socket IO(BIO、NIO)

    总结下Java socket IO.首先是各种IO的定义,这个定义似乎也是众说纷纭.我按照stackoverflow上面的解释: IO有两种分法:按照阻塞或者按照同步.按照阻塞,有阻塞IO和非阻塞IO ...

  7. (四:NIO系列) Java NIO Selector

    出处:Java NIO Selector 1.1. Selector入门 1.1.1. Selector的和Channel的关系 Java NIO的核心组件包括: (1)Channel(通道) (2) ...

  8. Java IO 和 NIO

    昨天面试问到了有关Java NIO的问题,没有答上来.于是,在网上看到了一篇很有用的系列文章讲Java IO的,浅显易懂.后面的备注里有该系列文章的链接.内容不算很长,需要两个小时肯定看完了,将该系列 ...

  9. Java中的NIO基础知识

    上一篇介绍了五种NIO模型,本篇将介绍Java中的NIO类库,为学习netty做好铺垫 Java NIO 由3个核心组成,分别是Channels,Buffers,Selectors.本文主要介绍着三个 ...

随机推荐

  1. 2.7、Android Studio使用翻译编辑器本地化UI

    如果你的应用支持多语言,你需要合理的管理你的翻译的string资源.Android Studio 提供了翻译编辑器来使查看和管理翻译的资源更加容易. 关于翻译编辑器 翻译后的资源在你的项目里保存在不同 ...

  2. iOS7 CookBook精彩瞬间(一)property、selector细节、__unused

    1.我们常常使用nonatomic,很多人只知道它的效率较高,却不知道其含义,其含义是非线程安全的,也就是说多线程修改时不加锁,可能出现多个线程先后修改而成为脏数据的情况. 2.unsafe_unre ...

  3. 带吸附效果的ViewPager(一)

    什么叫吸附效果?先看一个示例更为直观,借用网上的一个效果图: 类似这种效果的app很多,网上的实现方法也是很多,但各种重写各种监听又令人不胜其烦,今日突发奇想,顺着自己的思路实现了类似的效果,不敢独享 ...

  4. 08 ListView 优化的例子

    package com.fmy.homework; import java.util.List; import com.fmy.homework.httputil.HttpUtil; import c ...

  5. Android的ANR详解(原因和方案)

    ANR的定义 在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框 ...

  6. 微信公众号Unauthorized API function

    在进行微信公众号第三方开发的时候经常遇到这个问题,有两个原因: 1. 你的公众号没有这个api的功能(比如你是个人订阅号等). 2. 你的公众号有这个功能,但是你公众号没有进行认证. 具体可以查看微信 ...

  7. 初识WCF之使用配置文件部署WCF应用程序

    二月份的开头,小编依旧继续着项目开发之路,开始接触全新的知识,EF,WCF,MVC等,今天小编来简单的总结一下有关于WCF的基础知识,学习之前,小编自己给自己提了两个问题,WCF是什么?WCF能用来做 ...

  8. RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

            在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇 ...

  9. JAVA之旅(十八)——基本数据类型的对象包装类,集合框架,数据结构,Collection,ArrayList,迭代器Iterator,List的使用

    JAVA之旅(十八)--基本数据类型的对象包装类,集合框架,数据结构,Collection,ArrayList,迭代器Iterator,List的使用 JAVA把完事万物都定义为对象,而我们想使用数据 ...

  10. PHP解决中文乱码问题

    初学PHP,在汉字页面间传输和转换的时候,遇到了中文乱码问题. 究其原因乱码无外乎以下几种情况: 1.html页本身的乱码问题, 解决方法:纯html页使用<meta http-equiv=&q ...