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 之間關係的橋樑。