简介
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 之间关系的桥梁.
来源: https://www.cnblogs.com/kendoziyu/p/java-nio-selector-selectionkey-selectablechannel.html