Java 網路程式設計 —— 非阻塞式程式設計

2023-05-14 18:01:23

執行緒阻塞概述

在生活中,最常見的阻塞現象是公路上汽車的堵塞。汽車在公路上快速行駛,如果前方交通受阻,就只好停下來等待,等到公路順暢,才能恢復行駛。

執行緒在執行中也會因為某些原因而阻塞。所有處於阻塞狀態的執行緒的共同特徵:放棄 CPU,暫停執行,只有等到導致阻塞的原因消除,才能恢復執行,或者被其他執行緒中斷該執行緒會退出阻塞狀態,並且丟擲 InterruptedException

導致執行緒阻塞的原因主要有以下方面:

  • 執行緒執行了 Threadsleep(int n) 方法,執行緒放棄 CPU,睡眠 n ms,然後恢復執行
  • 執行緒要執行一段同步程式碼,由於無法獲得相關的同步鎖,只好進入阻塞狀態,等到獲取同步鎖再恢復執行
  • 執行緒執行了一個物件的 wait() 方法,進入阻塞狀態,只有等到其他執行緒執行了該物件的 notify()notifyAll() 方法,才可能將其喚醒
  • 執行緒執行 IO 操作或進行遠端通訊時,會因為等待相關的資源而進入阻塞狀態

進行遠端通訊時,在客戶程式中,執行緒在以下情況下可能進入阻塞狀態:

  • 請求與伺服器建立連線時,即當執行緒執行 Socket 的帶引數的構造方法,或執行 Socke 的 connect() 方法時,會進入阻塞狀態,直到連線成功,此執行緒才從 Socket 的構造方法或 connect() 方法返回

  • 執行緒從 Socket 的輸入流讀入資料時,如果沒有足夠的資料,就會進入阻塞狀態,直到讀到了足夠的資料,或者到達輸入流的末尾,或者出現了異常,才從輸入流的 read() 方法返回或異常中斷

    輸入流中有多少資料才算足夠呢?這要看執行緒執行的 read() 方法的類:

    • int read():只要輸入流中有 1 位元組,就算足夠
    • int read(byte[] buf):只要輸入流中的位元組數目與引數 buff 陣列的長度相同,就算足夠
    • String readLine():只要輸入流中有 1 行字元,就算足夠
  • 執行緒向 Socket 的輸出流寫一批資料時,可能會進入阻塞狀態,等到輸出了所有的資料,或者出現異常,才從輸出流的 write() 方法返回或異常中斷

  • 如果呼叫 Socket 的 setSoLinger() 方法設定了關閉 Socket 的延遲時間,那麼當執行緒執行 Socket 的 close() 方法時,會進入阻塞狀態,直到底層 Socket 傳送完所有剩餘資料或者超過了 setSoLinger() 方法設定的延遲時間,才從 close() 方法返回

在伺服器程式中,執行緒在以下情況下可能會進入阻塞狀態:

  • 執行緒執行 ServerSocket 的 accept() 方法,等待客戶的連線,直到接收到了客戶連線才從 accept() 方法返回
  • 執行緒從 Socket 的輸入流讀入資料時,如果輸入流沒有足夠的資料就會進入阻塞狀態
  • 執行緒向 Socket 的輸出流寫一批資料時,可能會進入阻塞狀態,等到輸出了所有的資料,或者出現異常,才從輸出流的 write() 方法返回或異常中斷

由此可見,無論是在伺服器程式還是客戶程式中,當通過 Socket 的輸入流和輸出流讀寫資料時,都可能進入阻塞狀態。這種可能出現阻塞的輸入和輸出操作被稱為阻塞 IO。與此對照,如果執行輸入和輸出操作時,不會發生阻塞,則稱為非阻塞 IO


非阻塞通訊的基本思想

假如同時要做兩件事:燒開水和煮粥

燒開水的步驟如下:

鍋子裡放水,開啟煤氣爐
等待水燒開 // 阻塞
關閉煤氣爐,把開水灌到水壺裡

煮粥的步驟如下:

鍋子裡放水和米,開啟煤氣爐
等待粥煮開 // 阻塞
調整煤氣爐,改為小火
等待粥煮熟 // 阻塞
關閉煤氣爐

為了同時完成兩件事,一種方案是同時請兩個人分別做其中的一件事,這相當於採用多執行緒來同時完成多個任務。還有一種方案是讓一個人同時完成兩件事,這個人應該善於利用一件事的空閒時間去做另一件事,這個人一刻也不應該閒著:

鍋子裡放水,開啟煤氣爐 // 開始燒開水
鍋子裡放水和米,開啟煤氣爐 // 開始煮粥
while(一直等待,直到有水燒開、粥煮開或粥煮熟事件發生) { // 阻塞
	if(水燒開)
		關閉煤氣爐,把開水灌到水壺裡;
	if((粥煮開)
		調整煤氣爐,改為小火;
	if(粥熟)
		關閉煤氣爐;
}

這個人不斷監控燒水和煮粥的狀態,如果發生了條件中任一事件就去處理,處理完一件事後繼續監控,直到所有的任務都完成

以上工作方式也可以被運用到伺服器程式中,伺服器程式只需要一個執行緒就能同時接收客戶的連線、接收各個客戶傳送的資料,以及向各個客戶傳送響應資料。伺服器程式的處理流程如下:

while(一直等待,直到有接收連線就緒事件、讀緒事件或寫就緒事件發生) { //阻塞
	if(有客戶連線)
		接收客戶的連線; // 非阻塞
	if(某個socket的輸入流中有可讀資料)
		從輸入流中讀資料; // 非阻塞
	if(某個socket的輸出流可以寫資料)
		向輸出流寫資料; // 非阻塞
}

以上處理流程採用了輪詢的工作方式,當某一種操作就緒,就執行該操作,否則就檢視是否還有其他就緒的操作可以執行。執行緒不會因為某一個操作還沒有就緒,就進入阻塞狀態,一直傻傻地在那裡等待這個操作就緒

為了使輪詢的工作方式順利進行,接收客戶的連線、從輸入流讀資料,以及向輸出流寫資料的操作都應該以非阻寒的方式執行。所謂非阻塞,指當執行緒執行這些方法時,如果操作還沒有就緒,就立即返回,而不會一直等到操作就緒


非阻塞通訊 API

java.nio.channels 包提供了支援非阻塞通訊的類,如下所述:

  • ServerSocketChannelServerSocket 的替代類,支援阻塞通訊與非阻塞通訊
  • SocketChannelSocket 的替代類,支援阻塞通訊與非阻塞通訊
  • Selector:為 ServerSocketChannel 監控接收連線就緒事件,為 SocketChannel 監控連
    接就緒、讀就緒和寫就緒事件
  • SelectionKey:代表 ServerSocketChannel 以及 SocketChannelSelector 註冊事件的控制程式碼。當一個 SelectionKey 物件位於 Selector 物件的 selected-keys 集合中,就表示與這個 SelectionKey 物件相關的事件發生了

ServerSocketChannelSocketChannel 都是 SelectableChannel 的子類,如圖所示,SelectableChannel 類及其子類都能委託 Selector 來監控它們可能發生的一些事件,這種委託過程也被稱為註冊事件過程

ServerSocketChannelSelector 註冊接收連線就緒事件的程式碼如下:

SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

SelectionKey 類的一些靜態常數表示事件型別,ServerSockerChamnel 只可能發生一種事件:

  • SelectionKey.OP_ACCEPT:接收連線緒事件,表示至少有了一個客戶連線,伺服器可以接收這個連線、

SocketChannel 可能發生以下三種事件:

  • SelectionKey.OP_CONNECT:連線就緒事件,表示客戶與伺服器的連線已經建立成功
  • SelectionKey.OP_READ:讀就緒事件,表示輸入流中已經有了可讀資料,可以執行讀操作了
  • SelectionKey.OP_WRITE: 寫就緒事件,表示已經可以向輸出流寫資料了

SocketChannel 提供了接收和傳送資料的方法:

  • read(ByteBuffer buffer):接收資料,把它們存放到引數指定的 ByteBuffer
  • write(ByteBuffer buffer):把引數指定的 ByteBuffer 中的資料傳送出去

ByteBuffer 表示位元組緩衝區,SocketChannelread()write() 方法都會操縱 ByteBufferByteBuffer 類繼承於 Buffer 類。ByteBuffer 中存放的是位元組,為了把它們轉換為字串還需要用到 Charset 類,Charset 類代表字元編碼,它提供了把位元組流轉換為字串(解碼過程)和把字串轉換為位元組流(編碼過程)的實用方法

下面分別介紹 BufferCharsetSelectableChannelServerSocketChannelSocketChannelSelectorSelectionKey 的用法


緩衝區 Buffer

資料輸入和輸出往往是比較耗時的操作,緩衝區從兩個方面提高 I/O 操作的效率:

  • 減少實際的物理讀寫次數
  • 緩衝區在建立時被分配記憶體,這塊記憶體區域一直被重用,這可以減少動態分配和回收記憶體區域的次數

java.nio 包公開了 Buffer 類的 API,使得 Java 程式可以直接控制和運用緩衝區,所有的緩衝區都有以下屬性:

  • 容量(capacity):表示緩衝區可以儲存多少資料
  • 極限(limit):表示緩衝區的當前終點,不能對緩衝區中超過極限的區域進行讀寫操作
  • 位置(position):表示緩衝區中下一個讀寫單元的位置

以上三個屬性的關係為:容量 > 極限 >= 位置 >= 0

緩衝區提供了用於改變以上三個屬性的方法:

// 把極限設為容量,把位置設為0
clear();
// 把極限設為位置,把位置設為 0
flip();
// 不改變極限,把位置設為0
rewind();

Buffer 類的 remaining() 方法返回緩衝區的剩餘容量,取值等於 極限 - 位置

Buffer 類的 compact() 方法刪除緩衝區內從 0 到當前位置 position 的內容,然後把從當前位置 position 到極限limit 的內容拷貝到 0 到 limit - position 的區域內

java.nio.Buffer 類是一個抽象類,不能被範例化。它共有 8 個具體的緩衝區類,其中最基本的緩衝區是 ByteBuffer,它存放的資料單元是位元組,ByteBufer 類並沒有提供公開的構造方法,但是提供了兩個獲得 ByteBuffer 範例的靜態工廠方法:

// 返回一個ByteBuffer物件,引數capacity指定緩衝區的容量
allocate(int capacity);
// 返回一個ByteBuffer物件,引數capacity指定緩衝區的容量
// 該方法返回的緩衝區被稱為直接緩衝區,能進一步提高 I/O 操作的速度
// 分配直接緩衝區的系統開銷很大,因此只有在緩衝區較大並且長期存在,或經常重用時,才使用該緩衝區
directAllocate(int capacity);

除 boolean 型別以外,每種基本型別都有對應的緩衝區類,包括 CharBufferDoubleBufferFloatBufferIntBufferLongBufferShortBuffer。在 CharBuffer 中存放的資料單元為字元,以此類推。還有一種緩衝區是 MappedByteBuffer,它是 ByteBuffer 的子類,能夠把緩衝區和檔案的某個區域直接對映

所有具體緩衝區類都提供了讀寫緩衝區的方法:

// 相對讀,從緩衝區的當前位置讀取一個單元的資料,讀完後把位置加1
get();
// 絕對讀,從引數 index 指定的位置讀取一個單元的資料
get(int index);
// 相對寫,向緩衝區的當前位置寫一個單元的資料,寫完後把位置加1
put(單後設資料型別 data);
// 絕對寫,向引數index指定的位置寫入一個單元的資料
put(int index, 單後設資料型別 data);

ByteBuffer 類不僅可以讀取和寫入一個單元的位元組,還可以讀取和寫入 int、char、float 和 double 等基本型別的資料,例如:

getInt()
getInt(int index)

以上不帶 index 引數的方法會在當前位置讀取或寫入資料,稱為相對讀寫。帶 index 引數的方法會在 index 引數指定的位置讀取或寫入資料,稱為絕對讀寫


字元編碼 Charset

java.nio.Charset 類的每個範例代表特定的字元編碼型別,把位元組序列轉換為字串的過程稱為解碼,把字串轉換為位元組序列的過程稱為編碼

Charset 類提供了編碼與解碼的方法:

// 對引數str指定的字串進行編碼,把得到的位元組序列存放在一個ByteBuffer物件並將其返回
ByteBuffer encode(String str);
// 對引數cb指定的字元緩衝區中的字元進行編碼,把得到的位元組序列存放在一個ByteBuffer物件並將其返回
ByteBuffer encode(CharBuffer cb);
// 對引數bb指定的ByteBuffer的位元組序列進行解碼,把得到的字元序列存放在一個CharBuffer物件並將其返回
CharBuffer decode(ByteBuffer bb);

Charset 類的靜態 forName(String encode) 方法返回一個 Charset 物件,引數 encode 指定編碼型別。例如以下程式碼建立了一個代表 GBK 編碼的 Charset 物件

Charset charset = Charset.forName("GBK");

Charset 類還有一個靜態方法 defaultCharset(),它返回代表本地平臺的預設字元編碼的 Charset 物件


通道 Channel

通道(Channel)用來連線緩衝區與資料來源或資料匯(即資料目的地),資料來源的資料經過通道到達緩衝區,緩衝區的資料經過通道到達資料匯

Channel 的主要層次結構如下:

java.nio.channels.Channel 介面只宣告了兩個方法:

// 關閉通道
close();
// 判斷通道是否開啟
isOpen();

Channel 介面的兩個最重要的子介面是 ReadableByteChannelWritableByteChannelReadableByteChannel 介面宣告了 read(ByteBuffer dst) 方法,該方法把資料來源的資料讀入引數指定的 ByteBuffer 緩衝區中。WritableByteChannel 介面宣告了 write(ByteBuffer src) 方法,該方法把引數指定的 ByteBuffer 緩衝區中的資料寫到資料匯中

ByteChannel 介面是一個便利介面,它擴充套件了 ReadableByteChannelWritableByteChannel 介面,因而同時支援讀寫操作

ScatteringByteChannel 介面擴充套件了 ReadableByteChannel 介面,允許分散地讀取資料。分散讀取資料指單個讀取操作能填充多個緩衝區,ScatteringByteChannel 介面宣告了 read(ByteBuffer[] dsts) 方法,該方法把從資料來源讀取的資料依次填充到引數指定的各個 ByteBuffer

GatheringByteChannel 擴充套件了 WritableByteChannel 介面,允許集中地寫入資料。集中寫入資料指單個寫操作能把多個緩衝區的資料寫到資料, GatheringByteChannel 介面宣告了 write(ByteBuffer[] srcs) 方法,該方法依次把引數指定的每個 ByteBuffer 中的數寫到資料匯

FileChannel 類是 Channel 介面的實現類,代表一個與檔案相連的通道。該類實現了 ByteChannelScatteringByteChannelGatheringByteChannel 介面,支援讀操作、寫操作、分散讀操作和集中寫操作。FileChannel 類沒有提供公開的構造方法,因此不能用 new 語句來構造它的範例。不過,在FileInputStreamFileOutputStreamRandomAccessFile 類中提供了 getChannel() 方法,該方法返回相應的 FileChannel 物件

SelectableChannel 也是一種通道,它不僅支援阻塞的 I/O 操作,還支援非阻塞的 I/OSelectableChannel 有兩個子類,ServerSocketChannelSocketChannelSocketChannel 還實現了 ByteChannel 介面,具有 read(ByteBuffer dst)write(ByteBuffer src) 方法

1. SelectableChannel 類

SelectableChannel 是一種支援阻塞 IO 和非阻塞 IO 的通道。在非阻塞模式下,讀寫資料不會阻塞,並且 SelectableChannel 可以向 Selector 註冊讀就緒和寫就緒等事件。Selector 負責監控這些事件,等到事件發生時,比如發生了讀就緒事件,SelectableChannel 就可以執行讀操作了

SelectableChannel 的主要方法如下:

// 當引數block為true,表示把SelectableChannel設為阻塞模式
// 當引數block為false時,表示把SelectableChannel設為非阻塞模式
// SelectableChannel預設採用阻塞模式
// 該方法返回SelectableChannel物件本身的參照,相當於return this
public SelectableChannel configureBlocking(boolean block) throws IOException
// 以下兩個方法都向Selector註冊事件
public SelectionKey register(Selector sel,int ops) throws ClosedChannelException
public SelectionKey register(Selector sel,int ops,Object attachment) throws ClosedChannelException

以下是 socketChannelSelector 註冊讀就緒和寫就緒事件

SelectionKey key = socketChannel.register(selector.SelectionKey.OP_READ | SelectionKey.OP_WRITE);

register() 方法返回一個 SelectionKey 物件,SeletionKey 被用來跟蹤被註冊的事件。第二個 register() 方法還有一個 Object 型別的引數 attachment,用於為 SelectionKey 關聯附件,當被註冊事件發生後,需要處理該事件時,可以從 SelectionKey 中獲得這個附件,該附件可用來包含與處理這個事件相關的資訊

2. ServerSocketChannel 類

ServerSocketChannel 繼承自 SelectableChannel,是 ServerSocket 的替代類,通過它的靜態方法 open() 來建立。每個 ServerSockeChannel 物件都與一個 ServerSocket 物件關,通過 socket() 方法返回與它關聯的 ServerSocket 物件。可通過以下方式把伺服器程序繫結到一個本地埠:

serverSocketChannel.socket().bind(port);

ServerSocketChannel 的主要方法如下:

// 返回一個ServerSocketChannel物件,該物件沒有與任何本地埠繫結,並且處於阻塞模式
public static ServerSocketChannel open() throws IOException
// 用於接收客戶的連線,如果處於非阻塞狀態,當沒有客戶連線時就立即返回null
public SocketChannel accept() throws IOException
// 返回ServerSocketChannel所能產生的事件,這個方法總是返回SelectionKey.OP_ACCEPT
public final int validOps()
// 返回ServerSocketChannel關聯的ServerSocket物件
public ServerSocket socket()

3. SocketChannel類

SockeChannel 可以被看作是 Socket 的替代類,SockeChannel 不僅繼承了 SelectableChannel,而且實現了 ByteChannelSockeChannel 同樣通過它的靜態方法 open() 來建立

public static SocketChannel open() throws IOException
// 帶引數的構造方法還會建立與遠端伺服器的連線
public static SocketChannel open(SocketAddress remote) throws IOException

SocketChannel 的主要方法如下:

// 返回ServerSocketChannel所能產生的事件,這個方法總是返回以下值
// SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE
public final int validOps()
// 返回SocketChannel關聯的Socket物件
public Socket socket()
// 建立遠端連線,當處於非阻塞模式時,如果立即連線成功返回true,否則返回false
public boolean connect(SocketAddress remote) throws IOException
// 判斷底層Socket是否已經建立遠端連線
public boolean isConnected()
// 判斷是否正在進行遠端連線,如果遠端連線操作已經開始,但還沒有完成,則返回true,否則返回false
// 也就是說,無論底層Socket還沒有開始連線,或者已經連線成功,該方法都會返回false
public boolean isConnectionPending()
// 試圖完成連線遠端伺服器的操作
// 非阻塞模式下,建立連線從呼叫connect()方法開始,到呼叫finishConnect()方法結束
// 如果在呼叫此方法之前連線已經建立,則立即返回true,否則立即返回false
// 阻塞模式下,如果連線操作還沒有完成,則會進入阻塞狀態,直到連線完成,或者出現IO異常
public boolean finishConnect) throws IOException
// 從Channel讀入若干位元組,存放到引數指定的ByteBuffer
// 假設ByteBuffer剩餘容量為r,阻塞模式下,該方法會爭取讀到r位元組
// 如果輸入流中不足r位元組,就進入阻塞狀態,直到讀入了r位元組,或者讀到了輸入流末尾,或者出現了IO異常
// 非阻塞模式下,該方法奉行能讀到多少資料就讀多少資料的原則
// 通道中的可讀資料,有可能不足r位元組,或者為0位元組,總是立即返回
// 該方法返回實際上讀入的位元組數,有可能為0,如果返回-1,表示讀到了輸入流的末尾
public int read(ByteBuffer dst) throws IOException
// 把引數src指定的ByteBuffer的位元組寫到Channel
// 假設ByteBuffer剩餘容量為r,阻塞模式下,該方法會爭取輸出r位元組
// 如果底層網路的輸出緩衝區不能容納r位元組,就進入阻塞狀態,直到輸出了r位元組,或者出現了IO異常
// 非阻塞模式下,該方法奉行能輸出多少資料就輸出多少資料的原則,有可能不足r位元組,或者為0位元組,總是立即返回
// 該方法返回實際上輸出的位元組,有可能為0
public int write(ByteBuffer src) throws IOException

Selector 類

只要 ServerSockerChannel 以及 SocketChannelSelector 註冊了特定的事件,Selector 就會監控這些事件是否發生。SelectableChannelregister() 方法負責註冊事件,該方法返回 SelectionKey 物件,該物件是用於跟蹤這些被註冊事件的控制程式碼

Selector 物件中會包含三種型別的 SelectionKey 的集合:

  • all-keys:當前所有向 Selector 註冊的 SelectionKey 的集合,Selectorkeys()
    法返回該集合
  • selected-keys:相關事件已經被 Selector 捕獲的 SelectionKey 的集合,Selector
    selectedKeys()方法返回該集合
  • cancelled-keys:已經被取消的 SelectionKey 的集合,Selector 沒有提供存取這
    種集合的方法

當執行 SelectableChannelregiste() 方法,會新建一個 SelectionKey 並加入 Selectorall-keys 集合中。如果關閉了與 SelectionKey 物件關聯的 Channel 物件,或者呼叫了 SelectionKey 物件的 cancel() 方法,那麼這個 SelectionKey 物件就會被加入 cancelled-keys 集合,表示已經被取消,在程式下一次執行 Selectorselect() 方法時,被取消的 SelectionKey 物件將從所有的集合(包括 all-keys 集合、selected-keys 集合和 cancelled-keys 集合)中被刪除

在執行 Selectorselect() 方法時,如果與 SelectionKey 相關的事件發生了,這個 SelectionKey 就被加入 selected-keys 集合中。程式直接呼叫 selected-keys 集合的 remove() 方法,或者呼叫它的 Iteratorremove() 方法,都可以從 selected-keys 集合中刪除一個 SelectionKey 物件

程式不允許直接通過集合介面的 remove() 方法刪除 all-keys 集合中的 SelectionKey 物件,這會導致 UnsupportedOperationException

Selector 類的主要方法如下:

// Selector的靜態工廠方法,建立一個Selector物件
public static Selector open() throws IOException
// 判斷Selector是否處於開啟狀態,Selector物件建立後就處於開啟狀態,當呼叫close()方法就進入關閉狀態
public boolean isOpen()
// 返回Seleclor的all-keys集合,包含了所有與Seclector關聯的SelectionKey物件
public Set<SelectionKey> keys()
// 返回相關事件已經發生的SelectionKey物件的數目
// 該方法採用非阻塞的工作方式,返回當前相關事件已經發生的SelectionKey物件的數目,如果沒有,就立即返回0
public int selectNow() throws IOException
// 返回相關事件已經發生的SelectionKey物件的數目
// 該方法採用阻塞的工作方式,如果一個也沒有,就進入阻塞狀態,直到出現以下情況之一,就會從select()返回:
// 1.至少有一個SelectionKey的相關事件已經發生
// 2.其他執行緒呼叫了Selector的wakeup()方法
// 3.當前執行select()方法的執行緒被其他執行緒中斷
// 4.超出了等待時間
public int select() throws IOException
public int select(long timeout) throws IOException
// 喚醒執行Selector的select()方法 
public Selector wakeup()
// 關閉 Selector
// 如果有其他執行緒正執行這個Selector的select()方法並且處於阻塞狀態,這個執行緒會立即返回
// close()方法使得Selector佔用的所有資源都被釋敗,所有關聯的SelectionKey都被取消
public void close() throws IOException

SelectionKey 類

SelectionKey 中定義了四種事件,分別用四個 int 型別的常數來表示:

  • SelectionKey.OP_ACCEPT:接收連線就緒事件,表示伺服器監聽到了客戶連線,伺服器可以接收這個連線了,常數值為 16
  • SeiectionKey.OP_CONNECT:連線就緒事件表示客戶與伺服器的連線已經建立成功,常數值為 8
  • SelectionKey.OP_READ:讀就緒事件,表示通道中已經有了可讀資料,可以執行讀操作了,常數值為 1
  • SelectionKey.OP_WRITE:寫就緒事件表示已經可以向通道寫資料了,常數值為 4

以上常數分別佔據不同的二進位制位,因此可以通過二進位制的或運算來將它們進行任意組合

一個 SelectionKey 物件中包含兩種型別的事件:

  • 所有感興趣的事件:通過 SelectableChannelregister() 方法註冊事件時,可以在引數中指定 SelectionKey 感興趣的事件

    SelectionKey key = socketChannel.register(selector,SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
    

    該程式碼錶示這個 SelectionKey 對讀就緒和寫就緒事件感興趣,與之關聯的 Selector 物件會負責監控這些事件

    SelectionKey 的帶引數的 interestOps(int ops) 方法也可以為 SelectionKey 物件增加一個感興趣的事件,如下程式碼所示:

    key.interestOps(SelectionKey.OP_WRITE);
    
  • 所有已經發生的事件:SeletionKeyreadyOps() 方法返回所有已經發生的事件,例如假定返回值為 SelectionKey.OP_WRITE | SelectionKey.OP_READ,表示讀就緒和寫就緒事件已經發生了,這意味著與之關聯的 SocketChannel 物件可以進行讀操作和寫操作了

SelectionKey 的主要方法如下:

// 返回與這個SelectionKey物件關聯的SelectableChannel物件
public SelectableChannel channel()
// 返回與這個SelectionKey物件關聯的Selector物件
public Selector selector()
// 判斷這個SelectionKey是否有效
// 當SelectionKey物件建立後,它就一直處於有效狀態
// 如果呼叫了它的cancel()方法,或關閉了與它關聯的SelectableChannel或Selector物件,它就失效
public boolean isValid()
// 使SelectionKey物件失效
public void cancel()
// 返回這個SelectionKey感興趣的事件
public int interestOps()
// 為SelectionKey增加感興趣的事件
public SelectionKey interestOps(int ops)
// 返回已經就緒的事件
public int readyOps()
// 判斯與之關聯的SocketChannel的讀就緒事件是否已經發生
public final boolean isReadable()
// 判斷與之關聯的SocketChannel的寫就緒事件是否已經發生
public final boolean isWritable()
// 判斷與之關聯的SocketChannel的連線就緒事件是否已經發生
public final boolean isConnectable()
// 判斷與之關聯的ServerSocketChannel的接收連線就緒事件是否已經發生
public final boolean isAcceptable()
// 使SelectionKey關聯一個附件,一個SelectionKey物件只能關聯一個Object型別的附件
// 如果多次呼叫該方法,則只有最後一個附件與SelectionKey物件關聯
public final Object attach(Object obj)
// 返回與SelectionKey物件關聯的附件
public final Object attachment()

Channels 類

Channels 類是一個簡單的工具類,提供了通道與傳統的基於 IO 的流、ReaderWriter 之間進行轉換的靜態方法

ReadableByteChannel newChannel(InputStream in) // 輸入流轉換成讀通道
WritableByteChannel newChannel(OutputStream out) // 輸出流轉換成寫通道
InputStream newInputStream(AsynchronousByteChannel ch) // 非同步通道轉換成輸入流
InputStream newInputStream(ReadableByteChannel ch) // 讀通道轉換成輸入流
OutputStream newOutputStream(AsynchronousByteChannel ch) // 非同步通道轉換成輸出流
OutputStream newOutputStream(WritableByteChannel ch) // 寫通道轉換成輸出流
Reader newReader(ReadableByteChannel ch,String csName) // 讀通道轉換成Reader,引數csName指定字元編碼
Reader newReader(ReadableByteChannel ch,Charset charset)//讀通道轉換成Reader.引數charset指定字元編碼
Reader newReader(ReadableByteChannel ch,CharsetDecoder dec, int minBufferCap) // 讀通道轉換成 Reader,引數dec指定字元解碼器,引數minBufferCap指定內部位元組緩衝區的最小容量
Writer newWriter(WritableByeChannel ch, String csName) // 寫通道轉換Writer.引數csName指定字元編碼
Writer newWriter(WritableByeChannel ch, Charset charset) // / 寫通道轉換Writer.引數charset指定字元編碼
Writer newWriter(WritableByeChannel ch, CharsetEncoder enc, int minBufferCap) // 寫通道轉換成Writer,引數dec指定字元解碼器,引數minBufferCap指定內部位元組緩衝區的最小容量

Socket 選項

從 JDK7 開始,SocketChannelServerSocketChannelAsynchronousSocketChannelAsynchronousServerSocketChannelDatagramChannel 都實現了新的 NetworkChannel 介面。NetworkChannel 介面的主要作用是設定和讀取各種 Socket 選項

NetworkChannel 介面提供了用於設定和讀取這些選項的方法:

<T> T getOption(SocketOption<T> name) // 獲取特定的Socket選項值
<T> NetworkChannel setOption(SocketOption<T> name, T value) // 設定特定的Socket選項
Set<SocketOption<?>> supportedOptions() // 獲取所有支援的Socket選項

SocketOptionl 類是一個泛型類,SocketOption<T> 中的 T 代表特定選項的取值型別,可選值包括 IntegerBooleanNetworkInterface

StandardSocketOptions 類提供了以下表示特定選項的常數:

SocketOption<NetworkInterface>  --  StandardSocketOptions.IP_MULTICAST_IF
SocketOption<Boolean>  --  StandardSocketOptions.IP_MULTICAST_LOOP
SocketOption<Integer>  --  StandardSocketOptions.IP_MULTICAST_TTL
SocketOption<Integer>  --  StandardSocketOptions.IP_TOS
SocketOption<Boolean>  --  StandardSocketOptions.SO_BROADCAST
SocketOption<Boolean>  --  StandardSocketOptions.SO_KEEPALIVE
SocketOption<Integer>  --  StandardSocketOptions.SO_LINGER
SocketOption<Integer>  --  StandardSocketOptions.SO_RCVBUF
SocketOption<Boolean>  --  StandardSocketOptions.SO_REUSEADDR
SocketOption<Boolean>  --  StandardSocketOptions.SO_REUSEPORT
SocketOption<Integer>  --  StandardSocketOptions.SO_SNDBUF
SocketOption<Boolean>  --  StandardSocketOptions.TCP_NODELAY