• NIO入门之多路复用选择器Selector


    简介

    Selector 是 java.nio.channels 包下的重要组件,阅读本文可以带你了解常用的 API。本文中把 Channel 翻译成信道,按照个人习惯也可以称作是通道、管道。
    Selector 的核心组件有 SelectableChannel、Selector、SelectionKey。
    selector-core-class
    多路复用选择器更多的是用在 Java 网络编程,网络编程的常用到就是 UDP / TCP 。SelectableChannel 的子类如下:
    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 方法总是返回固定的值。
    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 的注册方法,可以形成以下关系:
    selector-relationship

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

    Selector 操作状态说明

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

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

    总结

    Selector、SelectableChannel、SelectionKey 是 Java NIO 多路复用中的核心组件。
    多路复用技术主要用于网络编程,SelectableChannel 其主要实现包含 DatagramChannel、ServerSocketChannel 和 SocketChannel。

    • Selecter 是 SelectableChannel 在设置非阻塞情况下使用的多路复用选择器。
    • SelectableChannel 支持注册事件的信道。
    • SelectionKey 是注册事件后,建立 Selector 与 SelectableChannel 之间关系的桥梁。
  • 相关阅读:
    oracle数据库查看修改字符集问题
    C/C++内存问题检查利器—Purify (五)
    C/C++内存问题检查利器—Purify (四)
    C/C++内存问题检查利器—Purify (三)
    Oracle 字符集的查看和修改
    C/C++内存问题检查利器—Purify (二)
    linux内存管理之活动内存区
    C/C++内存问题检查利器—Purify (一)
    postman——基础操作——API请求与响应——API响应
    postman——基础操作——History选项卡
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/java-nio-selector-selectionkey-selectablechannel.html
Copyright © 2020-2023  润新知