亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Java NIO Selector 介紹

1. 前言

前面小節介紹 Java TCP Socket 編程時,我們遇到了“創建的 TCP server 不能同時接收多個客戶端請求”的問題。我們的解決方案是采用多線程模型,即每線程模型或者是線程池模型。然而多線程模型會創建大量的線程,消耗大量系統資源。后來進入了 Java NIO 編程的學習,我們說編寫高性能服務器必須采用非阻塞式 Socket 編程。然而,通過 Java NIO 的編程示例可以發現:相比阻塞式 Socket 編程,非阻塞式 Socket 編程的難度大了一個數量級。我們需要應用好 I/O 多路事件處理機制,需要處理好數據收發的各種情況,而 I/O 事件多路復用機制是整個非阻塞 Socket 編程的核心。

其實,I/O 多路復用機制(I/O Multiplex)最早是由操作系統提供的,有一套專用的系統 API。目前主流操作系統提供的 I/O 多路復用 API 如下:

  • select,是通用機制,Windows、Unix-like 系統都支持。
  • poll, 是 UNIX-like 系統支持。
  • devpoll,是 SUN Solaris 系統支持。當然,SUN 公司已經不存在了。
  • epoll, 是 Linux 系統支持的主流機制。
  • Kqueue,是 freebsd 內核支持的機制,Mac OS、IOS 系統也支持。
  • IOCP,是 Windows 系統支持的機制。

對于 Java 來說,也有自己的 I/O 多路復用機制,那就是 Java NIO Selector

2. Java NIO Selector 工作原理

Java NIO 四個核心的組件分別是 Selector、SocketChannel、ServerSocketChannel、SelectionKey。Selector 是 I/O 事件反應器,是動力源。SocketChannel、ServerSocketChannel、SelectionKey 都是功能組件,它們之間互相配合,如下:

圖片描述

  • 首先創建一個 Selector 對象,然后調用它的 select 方法,進入事件等待狀態。
  • 對于服務器來說,需要創建 ServerSocketChannel 對象,然后調用它的 register 方法,將 SelectionKey.OP_ACCEPT 事件注冊到 Selector,準備監聽新的客戶端連接。
  • 如果 Selector 監聽到新的客戶端連接請求,SelectionKey.OP_ACCEPT 事件就會產生。調用 ServerSocketChannel 的 accept 方法,返回一個 SocketChannel 對象,需要將 SocketChannel 的 SelectionKey.OP_READ 事件注冊到 Selector。
  • 在上面兩步中, ServerSocketChannel 和 SocketChannel 都提供了 register 方法,返回值是 SelectionKey。SelectionKey 中綁定了上下文信息。
  • 如果 Selector 監聽到 I/O 事件,它的 select 方法就會返回??梢哉{用 Selector 的 selectedKeys 方法,返回一個 SelectionKey 數組,包含了所有產生了 I/O 事件的 SocketChannel。遍歷這個數組,逐個處理相應的 I/O 事件。

3. Java Selector API 介紹

3.1 創建實例

Selector 只聲明了一個 protected 構造方法,構造 Selector 實例只能通過它的工廠方法 open,聲明如下:

public static Selector open() throws IOException

3.2 注冊事件

Selector 接口并沒有聲明 register 方法,而是通過它的抽象實現類提供了一個 abstract protected 方法,也沒有對外暴露。聲明如下:

protected abstract SelectionKey register(AbstractSelectableChannel ch,int ops, Object att);

Selector 的 register 機制最終委派給了 AbstractSelectableChannel 類。為此,我們要想將 Channel 注冊到 Selector,需要調用 AbstractSelectableChannel 的 register 方法。聲明如下:

public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException

參數說明:

  • sel 是預先創建的 Selector 對象。
  • ops 表示需要注冊的具體事件。支持的事件類型如下:
    - SelectionKey.OP_ACCEPT   表示監聽客戶端的連接,用于服務器
    - SelectionKey.OP_CONNECT  表示非阻塞式客戶端連接過程,用于客戶端
    - SelectionKey.OP_READ     表示監聽讀事件
    - SelectionKey.OP_WRITE    表示監聽寫事件
    
  • att 用于保存上下文對象。

3.3 監聽事件

Selector 提供了 select 方法用于監聽 I/O 事件,聲明如下:

public abstract int select() throws IOException
public abstract int select(long timeout) throws IOException

當沒有 I/O 事件產生時,調用 select 方法的線程會被阻塞。如果你調用無參 select 方法,線程進入等待狀態,直到有 I/O 事件發生才返回。如果你調用包含了 timeout 參數的 select 方法,線程會在 timeout 超時,或者是有 I/O 事件發生返回。select 的返回值表示產生了 I/O 事件的 SelectionKey 的個數。

3.4 遍歷事件

當有 I/O 事件發生,Selector 的 select 方法會返回??梢酝ㄟ^ Selector 的 selectedKeys 方法,獲取所有產生了 I/O 事件的 SelectionKey。聲明如下:

 public abstract Set<SelectionKey> selectedKeys()

方法的返回值是一個 SelectionKey 類型的集合,我們需要遍歷此集合,逐個處理。遍歷的方法如下:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if (key != null) {
        if (key.isAcceptable()) {
            // ServerSocketChannel 接收了一個新連接
        } else if (key.isConnectable()) {
            // 表示一個新連接建立
        } else if (key.isReadable()) {
            // Channel 有準備好的數據,可以讀取
        } else if (key.isWritable()) {
            // Channel 有空閑的 Buffer,可以寫入數據
        }
    }
    keyIterator.remove();
}

3.5 SelectionKey 介紹

SelectionKey 是由 AbstractSelectableChannel 的 register 方法返回的,主要包含一個事件類型和上下文對象。SelectionKey 提供了一組方法,用以識別 I/O 事件類型。聲明如下:

public final boolean isAcceptable()
public final boolean isConnectable()
public final boolean isReadable()
public final boolean isWritable()

可以通過 SelectionKey 的 channel 方法,獲取關聯的 Channel,聲明如下:

public abstract SelectableChannel channel()

可以通過 SelectionKey 的 attachment 方法,獲取關聯的上下文對象。

public final Object attachment()

SelectionKey 的各個方法相對簡單,容易理解,我們在前面小節多次提到,不再贅述。

4. 總結

本小結主要是介紹 Java NIO Selector 機制的工作原理。關于 Selector 機制,不僅 Java 支持,各大操作系統都有支持,是編寫高性能服務器的利器。一般在依賴倒轉模型中,充當動力源、反應器的角色。