简介

Selector 是 java.nio.channels 包下的重要组件,阅读本文可以带你了解常用的 API。本文中把 Channel 翻译成信道,按照个人习惯也可以称作是通道、管道。

Selector 的核心组件有 SelectableChannel、Selector、SelectionKey。



多路复用选择器更多的是用在 Java 网络编程,网络编程的常用到就是 UDP / TCP 。SelectableChannel 的子类如下:

  • 抽象类 SelectableChannel,它可以通过 Selector 多路复用。要使用多路复用功能,首先要注册。SelectableChannel 通过 register(Selector, int) 方法注册到 Selector 中,并返回 SelectionKey 作为注册代表。
  • 抽象类 AbstractSelectableChannel ,它实现了 configureBlocking() 和 register() 方法,分别用于设置阻塞模式和注册事件
  • 抽象类 DatagramChannel 提供 UDP 信道服务。
  • 抽象类 ServerSocketChannel 提供 TCP 信道服务。主要用于服务器端
  • 抽象类 SocketChannel 代表连接服务端与客户端的信道。通过 ServerSocketChannel.accept() 获得。当服务端接收到一个来自客户端的连接时就会产生一个信道。

各信道支持的事件

通过 AbstractSelectableChannel#validOps() 方法,可以返回一个操作集合(类型是 int)。这个集合表示该信道支持的操作。int 的每一个 bit 位代表信道是否支持某类操作。

同一个 SelectableChannel 的完全实现类的 validOps 方法总是返回固定的值。

  • 目前 Selector 支持的事件类型是 4 种,分别是 SelectionKey.OP_ACCEPT、SelectionKey.OP_CONNECT、SelectionKey.OP_WRITE、SelectionKey.OP_READ,分别表示接受连接,主动连接,写,读
OP_ACCEPT OP_CONNECT OP_WRITE OP_READ
DatagramChannel 不支持 不支持 支持 支持
ServerSocketChannel 支持 不支持 不支持 不支持
SocketChannel 不支持 支持 支持 支持

注册事件及异常

AbstractSelectableChannel#register 方法可以注册指定事件到 Selector 中,并且还会保存注册成功后返回的 SelectionKey。

public final SelectionKey register(Selector sel, int ops, Object att)
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
// 从数组 SelectionKey[] 寻找选择器 sel 对应的 SelectionKey
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// 新注册一个 SelectionKey
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
// Selector 注册当前信道,并返回 SelectionKey
k = ((AbstractSelector)sel).register(this, ops, att);
// 将 SelectionKey 保存到数组中
addKey(k);
}
}
return k;
}
}

从这段源码中,我们还会留意到我们写代码常常会出现的几个异常:

  • 如果我们向 SelectableChannel 实例对象注册它不支持的事件,抛出 IllegalArgumentException
  • 我们注册事件前,没有调用 configureBlocking(false) 设置非阻塞,抛出 IllegalBlockingModeException

    以上这两个是首次运用 Selector 时最常碰到的异常,另外还有一些其他的异常,比如
  • 关闭 SelectorChannel 后,注册事件,抛出 ClosedChannelException
@Test(expected = ClosedChannelException.class)
public void ClosedChannelException() throws IOException {
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.close();
channel.register(selector, SelectionKey.OP_WRITE);
}
  • 关闭 Selector 后,注册事件,抛出 ClosedSelectorException
@Test(expected = ClosedSelectorException.class)
public void ClosedSelectorException() throws IOException {
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
Selector selector = Selector.open();
selector.close();
channel.register(selector, SelectionKey.OP_READ);
}
  • 取消 SelectionKey 后,注册事件,抛出 CancelledKeyException
@Test(expected = CancelledKeyException.class)
public void CancelledKeyException() throws IOException {
DatagramChannel channel = DatagramChannel.open();
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
key.cancel();
// selector.selectNow(); // 如果刷新一下就不会抛出异常
channel.register(selector, SelectionKey.OP_WRITE);
}

通过 SelectableChannel#register 的注册方法,可以形成以下关系:

  • 先看标识“五角星”的 AbstractSelectableChannel,包含一个可扩容的 SelectionKey 数组,通过数组元素对应多个不同的 Selector
  • 再看标识“三角形”的 SelectionKey,通过这个对象,可以找到注册时对应的 selector 和 channel
  • 然后看标识“五角星”的 Selector,包含一个 HashSet<SelectionKey>,通过集合保存与多个 SelectableChannel 的对应关系
  • 总而言之,SelectableChannel 和 Selector 通过 SelectionKey 形成了多对多的关系

Selector 操作状态说明

下图是常见的 Selector 操作及其键集的变化示意图:

  • keys 键集,保存所有注册的 SelectionKey 的集合。
  • selectedKeys 就绪键集,保存所有准备就绪的键,通过 select 方法增加
  • cancelledKeys 取消键集,保存所有已取消的键,通过 select 方法,可以从 keys 中清除所有取消键,同时清空取消键集。

总结

Selector、SelectableChannel、SelectionKey 是 Java NIO 多路复用中的核心组件。

多路复用技术主要用于网络编程,SelectableChannel 其主要实现包含 DatagramChannel、ServerSocketChannel 和 SocketChannel。

  • Selecter 是 SelectableChannel 在设置非阻塞情况下使用的多路复用选择器。
  • SelectableChannel 支持注册事件的信道。
  • SelectionKey 是注册事件后,建立 Selector 与 SelectableChannel 之间关系的桥梁。

NIO入门之多路复用选择器Selector的更多相关文章

  1. NIO 选择器 Selector

    选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能.就像在第一章中描述的那样,就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/O 通道(Channels).C/C++代码的 ...

  2. 第二章 NIO入门

    传统的同步阻塞式I/O编程 基于NIO的非阻塞编程 基于NIO2.0的异步非阻塞(AIO)编程 为什么要使用NIO编程 为什么选择Netty 第二章 NIO 入门 2.1 传统的BIO编程 2.1.1 ...

  3. NIO 入门(转)

    NIO 入门 Greg Travis2003 年 11 月 17 日发布 分享此页面 WeiboGoogle+用电子邮件发送本页面 20 在开始之前 关于本教程 新的输入/输出 (NIO) 库是在 J ...

  4. NIO入门-----01

    package com.sico.pck01_nio; import java.nio.ByteBuffer; import org.junit.Test; /**  * @author Sico   ...

  5. 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!

    本文原题“<NIO 入门>,作者为“Gregory M. Travis”,他是<JDK 1.4 Tutorial>等书籍的作者. 1.引言 Java NIO是Java 1.4版 ...

  6. NIO入门

    NIO:Non-blocking IO,即非阻塞式IO. 标准的IO基于字节流和字符流进行操作. 而NIO基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从Channel读取到Bu ...

  7. Java NIO入门(二):缓冲区内部细节

    Java NIO 入门(二)缓冲区内部细节 概述 本文将介绍 NIO 中两个重要的缓冲区组件:状态变量和访问方法 (accessor). 状态变量是前一文中提到的"内部统计机制"的 ...

  8. Java NIO入门

    NIO入门 前段时间在公司里处理一些大的数据,并对其进行分词.提取关键字等.虽说任务基本完成了(效果也不是特别好),对于Java还没入门的我来说前前后后花了2周的时间,我自己也是醉了.当然也有涉及到机 ...

  9. Flume-NG源码阅读之SourceRunner,及选择器selector和拦截器interceptor的执行

    在AbstractConfigurationProvider类中loadSources方法会将所有的source进行封装成SourceRunner放到了Map<String, SourceRun ...

随机推荐

  1. 安装Centos 7 并且配置远程登录

    安装: 1.安装VMware fusion.https://www.vmware.com/cn/products/fusion/fusion-evaluation.html 2.下载centos 7 ...

  2. JDK8--01:JDK8简介

    一.新特性1.lambda表达式(重点)2.函数式接口3.方法引用与构造器引用4.Stream API(重点)5.接口中的默认方法和静态方法6.新时间日期API7.其他新特性 二.特点: 1.速度更快 ...

  3. js页面跳转的问题(跳转到父页面、最外层页面、本页面)

    "window.location.href"."location.href"是本页面跳转 "parent.location.href"是上一 ...

  4. Redis系列(八):发布与订阅

    Redis的发布与订阅,有点类似于消息队列,发送者往频道发送消息,频道的订阅者接收消息. 1. 发布与订阅示例 首先,在本机开启第1个Redis客户端,执行如下命令订阅blog.redis频道: SU ...

  5. 认识Eureka (F版)

    Spring Cloud 为开发者提供了在分布式系统中的一些常用的组件(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁定,决策竞选,分布式会话集群状态).使用Sprin ...

  6. MySQL 查询 存储过程 视图 触发器 函数 索引 建表语句 数据库版本 当前登录用户 当前数据库名称

    MySQL 查询 存储过程 视图 触发器 函数 索引 建表语句 数据库版本 当前登录用户 当前数据库名称   INFORMATION_SCHEMA.TABLES INFORMATION_SCHEMA. ...

  7. P3261 [JLOI2015]城池攻占 题解

    题目 小铭铭最近获得了一副新的桌游,游戏中需要用 \(m\) 个骑士攻占 \(n\) 个城池.这 \(n\) 个城池用 \(1\) 到 \(n\) 的整数表示.除 \(1\) 号城池外,城池 \(i\ ...

  8. SqlLite用SQLiteTransaction快速导入数据

    mysql与sql server都有整表导入的类库,但是查遍了资料发现sqlLite没有,除非自己去写个,发现用SQLiteTransaction导入数据也很快,附上代码 /// <summar ...

  9. redis源码学习之工作流程初探

    目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...

  10. Python pip 国内镜像大全及使用办法

    Python pip 国内镜像大全及使用办法 一.国内镜像 清华 https://pypi.tuna.tsinghua.edu.cn/simple 豆瓣 pip install -i http://p ...