NIO入門之多路複用選擇器Selector
阿新 • • 發佈:2020-07-22
簡介
Selector 是 java.nio.channels
包下的重要元件,閱讀本文可以帶你瞭解常用的 API。本文中把 Channel 翻譯成通道,按照個人習慣也可以稱作是通道、管道。
Selector 的核心元件有 SelectableChannel、Selector、SelectionKey。
多路複用選擇器更多的是用在 Java 網路程式設計,網路程式設計的常用到就是 UDP / TCP 。SelectableChannel 的子類如下:
- 抽象類 SelectableChannel,它可以通過 Selector 多路複用。要使用多路複用功能,首先要註冊。SelectableChannel 通過
register(Selector, int)
- 抽象類 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 之間關係的橋樑。