netty补充NIO的SelectableChannel和SelectorProvider

SelectableChannel作为nio选择器和通道的关键

先看官方描述

/**
 * A channel that can be multiplexed via a {@link Selector}.
 * <p> In order to be used with a selector, an instance of this class must
 * first be <i>registered</i> via the {@link #register(Selector,int,Object)
 * register} method.  This method returns a new {@link SelectionKey} object
 * that represents the channel's registration with the selector.
 *其他略。。。
 */

简单总结:

它通道的注册使用大致过程如下:

  1. 新建通道 open方法
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

  2. 将通道已经通道感兴趣的事件注册到选择器Selector上

  3. 通过SelectKey获得需要处理的通道,然后对通道进行处理
    关闭一个已经注册的SelectableChannel需要两个步骤:
    *上面channel的父类就是SelectableChannel了
    它通道的取消大致过程如下:

  4. 取消注册的key,这个可以通过SelectionKey.cancel方法,也可以通过SelectableChannel.close方法,或者中断阻塞在该channel上的IO操作的线程来做到。

  5. 后续的Selector.selectXXX方法的调用才真正地关闭 本地Socket。
    因而,如果,如果在取消SelectionKey后没有调用到selector的select方法(因为Client一般在取消key后, 我们都会终止调用select的循环,当然,server关闭一个注册的channel我们是不会终止select循环的),那么本地socket将进入CLOSE-WAIT 状态(等待本地Socket关闭)。简单的解决办法是在 SelectableChannel.close方法之后调用Selector.selectNow方法,类似:
    Selector sel;
    SocketChannel sch;
    // …
    sch.close();
    sel.selectNow();
    实现类

    有关UDP协议的:DatagramChannel
    有关SCTP协议的:SctpChannel、SctpMultiChannel、SctpServerChannel
    [有关TCP协议的:ServerSocketChannel、SocketChannel
    有关管道的:SinkChannel、SourceChannel这两个抽象类定义在java.nio.channels.Pipe类中
    仅以SocketChannel和ServerSocketChannel分析提供如下类图:
    SocketChannel和ServerSocketChannel,两者的父类是SelectableChannel,类图结构如下:
    ServerSocketChannel的类图


    图片.png

    SocketChannel的类图


    图片.png

SelectorProvider作为选择器的核心部分

  • 顾名思义作为选择器的生产类,那么他是如何做到呢
  1. 通常我们使用Selector 创建一个新的选择器

Selector selector = Selector.open();
跟踪selector的源码如下:

    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

继续往下跟踪就是我们要介绍的关键了SelectorProvider.provider()

 public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
//AccessController.doPrivileged属于特权操作,下面详说
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
 //loadProviderFromProperty方法是通过JDK的参数//-Djava.nio.channels.spi.SelectorProvider=class设置的class来反射构造SelectorProvider
                            if (loadProviderFromProperty())
                                return provider;
//loadProviderAsService从jar中的目录META-INF/services配置文件中找参数//java.nio.channels.spi.SelectorProvider=class设置的第一个class来反射构造SelectorProvider
                            if (loadProviderAsService())
                                return provider;
//最后都没有则调用不同操作系统版本的JDK里自带的sun.nio.ch.DefaultSelectorProvider来创建SelectorProvider
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

先解释下AccessController访问控制类的三个功能

1、根据当前有效的安全策略,决定是允许还是拒绝对关键系统资源的访问,
2、将代码标记为“特权”,从而影响后续访问确定
3、获取当前调用上下文的“快照”,因此可以针对保存的上下文进行来自不同上下文的访问控制决策。

上面的特权操作主要使用了功能2,部分javadoc中伪代码如下:

//类注释
/**
 * somemethod() {
 *     ...normal code here...
 *     String user = AccessController.doPrivileged(
 *         new PrivilegedAction<String>() {
 *         public String run() {
 *             return System.getProperty("user.name");
 *             }
 *         });
 *     ...normal code here...
 * }}
* 其他略。。
**/
public final class AccessController {
//方法注释
 /**
     * Performs the specified {@code PrivilegedAction} with privileges
     * enabled. The action is performed with <i>all</i> of the permissions
     * possessed by the caller's protection domain.
     * 其他略。。
     */
//简言之就是让调用者有权执行方法代码中的action
    @CallerSensitive
    public static native <T> T doPrivileged(PrivilegedAction<T> action);
//其他略。。
}

总结SelectorProvider的创建分三步进行:
(1)由JDK的参数-Djava.nio.channels.spi.SelectorProvider=class设置的class来反射构造SelectorProvider,找不到就跳转到步骤(2)
(2)从jar中的目录META-INF/services配置文件中找参数java.nio.channels.spi.SelectorProvider=class设置的第一个class来反射构造SelectorProvider,找不到就跳转到步骤(3)
(3)调用不同操作系统版本的JDK里自带的sun.nio.ch.DefaultSelectorProvider来创建SelectorProvider
一般都会走到最后的步骤(3),而这个步骤里创建的SelectorProvider在各个操作系统对应的JDK里各不相同。sun.nio.ch.DefaultSelectorProvider这个类最终编译后放置在JDK的安装根目录下的jre/lib/rt.jar里。

  • 该方法返回系统范围默认选择器提供程序,那么系统默认的选择器和系统有关吗,答案是肯定的

不通系统的DefaultSelectorProvider实现

  • Windows
public class DefaultSelectorProvider {
//
    public static SelectorProvider create() {
        return new WindowsSelectorProvider();
    }
}
  • MAC
public class DefaultSelectorProvider {
 public static SelectorProvider create()
  {
    return new KQueueSelectorProvider();
  }
}
  • Linux
public class DefaultSelectorProvider {
public static SelectorProvider create()
  {
    String str1 = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
 
    if ("SunOS".equals(str1)) {
      return new DevPollSelectorProvider();
    }
 
    if ("Linux".equals(str1)) {
      String str2 = (String)AccessController.doPrivileged(new GetPropertyAction("os.version"));
      String[] arrayOfString = str2.split("\\.", 0);
      if (arrayOfString.length >= 2) {
        try {
          int i = Integer.parseInt(arrayOfString[0]);
          int j = Integer.parseInt(arrayOfString[1]);
          if ((i > 2) || ((i == 2) && (j >= 6))) {
            return new EPollSelectorProvider();
          }
        }
        catch (NumberFormatException localNumberFormatException)
        {
        }
      }
    }
    return new PollSelectorProvider();
  }
}

那么为什么会出现几种不同的实现方式呢?原因在于
不同操作系统的I/O多路复用选择器各自内核实现不同,目前有select、poll、epoll、kqueue四种实现

操作系统下JDK JDK里SelectorProvider实现
windows JDK sun.nio.cn.WindowsSelectorProvider
MAC JDK sun.nio.ch.KQueueSelectorProvider
Linux JDK 1)os.name=SunOS时,sun.nio.ch.DevPollSelectorProvider
2)os.name=Linux并且os.version版本号中第一个版本大于2或者第一个版本号等于2且第二个版本大于6时,
sun.nio.chEPollSelectorProvider
Linux kernels 2.6内核版本及以后使用epoll进行支持;
Linux kernels 2.6内核版本之前使用poll进行支持;
比如Linux version 3.10.0-327.el7.x86_64就是用的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容

  • # Java NIO # Java NIO属于非阻塞IO,这是与传统IO最本质的区别。传统IO包括socket和文...
    Teddy_b阅读 595评论 0 0
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,555评论 1 143
  • Android网络编程 目录 1、Java NIO 介绍 NIO是java New IO的简称,在jdk1.4里提...
    香沙小熊阅读 4,741评论 0 6
  • JAVA NIO基础 ...
    文思li阅读 617评论 0 3
  • NIO概述 Java NIO全称为Non-blocking IO或者New IO,从名字我们知道NIO是非阻塞的I...
    zhong0316阅读 601评论 0 7