1. 程式人生 > 實用技巧 >NIO入門之多路複用選擇器Selector

NIO入門之多路複用選擇器Selector

簡介

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