java之NIO select基本设计思路梳理

来源:互联网 发布:关于孙悟空的网络歌曲 编辑:程序博客网 时间:2024/06/05 06:29

总结:

  1. 多路复用概念:允许一个线程阻塞等待多个fd文件描述符的集合,只要任意一个有数据就返回。举个例子,大楼有许多门,保安晚上为了防止窃贼,最简单的方法就是一直巡视每个门看是否被打开了。但保安想偷懒睡觉,于是就用一条有铃铛的绳子绑在所有的门把手上,只要任何一个门打开了,铃铛就会想,保安就会知道某个门被打开了。
  2. 操作系统对多路复用的支持:多路复用是OS的IO中一个比较重要的概念,在windows和linux等都有支持,常见的是poll或者select函数。
  3. java对多路复用的支持:java需要提供对OS的poll等的封装,并封装一些复杂逻辑解决某个问题:
    1. 封装1024个channel的限制:
      1. 起因:多路复用使用了fd文件描述符集合,是FD_SET这个数据结构,在linux的内容中定义为1024,即除非重新编译内核,否则一个线程调用select最多监听1024个fd。
      2. 为何会有那么多channel:
        1. 注册到Selector上的Channel非常多,例如一个长连接服务器可能要同时维持数十万条连接;
        2. 过期或失效的Channel没有及时关闭,因而对应的记录会一直留在fdToKey中,时间久了就会越积越多;
        3. 如果tcp发包堆积导致IO框架的空闲连接检测机制失效,无法及时检测并关闭空闲的连接,则有可能导致fdToKey变大。
        4. .....
      3. 实现差异和注意点:Unix下是用bit位,socket的值做下标,所以1024是下标最大值。windows下是socket值的数组,遍历的,所以2014是最大个数。参见http://blog.csdn.net/adam040606/article/details/46833617
      4. java适应性改造:由于有1024的限制,但需要对用户屏蔽此限制(?),即select内部用多个线程监听,任意一个有数据则全部返回。即,当用户线程注册了超过1024个channel并select时,select代码中要对每1024个channel新分配一个线程(helperthread)使用系统select来监听(主线程监听0-1023),任意一个返回则所有线程返回,随后select返回。
    2. select在监听数据的时候,同时还需要监听控制信号,即支持signal唤醒wakeup:
      1. 目的:主要是为了唤醒阻塞在selector.select上的线程,让该线程及时去处理其他事情,例如注册channel,改变interestOps、判断超时等等。这些都是控制信号signal。
      2. signal的缺陷:OS的信号signal的deliver类似异步交付存在的问题:即直接丢给目的地,而不管目的地是否有人来取,进一步,这里的信号是一次性的而非持续缓存的物体,即相当于敲一下门而不是把包裹实体放到门口一直存在。所以,如果敲门的时候没有人,那么这个敲门的信息就被永远miss了。(锁机制中park和unpark对传统wait notify的改进类似,参考《java多线程编程-ReentrantLock笔记》)于是,如果signal要给select()发消息,而signal在select()执行前就被捕获了,那么select()会丢失这个信号。
      3. pipe替代signal:pipe可以唤醒接收端线程,可以实现wakeup。同时,由于pipe的内容不是一次性的会一直缓存不丢失,避免了signal的问题。
      4. windows使用loopback connection的socketChannel模拟pipe:在windows中,由于OS没有实现pipe(?),所以需要用一些机制实现,目前采用建立socket连接,一个serverfd,一个clientfd。serverfd注册到selector中,控制信号通过发送到clientfd实现唤醒select()。
    3. 处理fd集合等的cancel操作
  4. java中fd集合介绍:
    1. 实现机制:简单来说,可以理解为两个array,第一个array(FD_OFFSET首地址)保存要监听的fd,第二个array(EVENT_OFFSET首地址)在同样的index位置保存该fd需要监听的事件。即(fd,事件)才能唯一确定。这个事件,就是selector中的interestOps。
    2. 上文介绍的用于wakeup的控制channel,就是通过addWakeupSocket向fd集合中index=0的位置填写的控制channel。
    3. 具体来说是pollArray这个结构,通过unsafe.allocateMemory(size + ps)分配的。
  5. 其他:
    1. 回到最开始的门把手的比喻,绑定了绳子的,即需要监听的每个门,在java中都作为一个selectionkey结构,该结构包含这道门和门后的进入走道channel信息,也包含用户对这道门感兴趣的事件interestOps信息等。
    2. selectionkey如果有事件发生,通常有两种响应机制,第一种是同步机制,从select()返回后(可以取到所有有事件的selectionkey的集合,即selectedkeys),接着执行代码响应;第二种是异步机制,直接在selectionkey调用attach绑定事件响应函数处理(?)。

0 0