主頁 > 後端開發 > Java 網路編程 —— 非阻塞式編程

Java 網路編程 —— 非阻塞式編程

2023-05-15 07:34:46 後端開發

執行緒阻塞概述

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

執行緒在運行中也會因為某些原因而阻塞,所有處于阻塞狀態的執行緒的共同特征:放棄 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

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552418.html

標籤:其他

上一篇:Java的執行緒

下一篇:返回列表

標籤雲
其他(159012) Python(38129) JavaScript(25421) Java(18034) C(15226) 區塊鏈(8265) C#(7972) AI(7469) 爪哇(7425) MySQL(7184) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5340) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4572) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2433) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1972) 功能(1967) Web開發(1951) HtmlCss(1936) python-3.x(1918) C++(1915) 弹簧靴(1913) xml(1889) PostgreSQL(1876) .NETCore(1860) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Java 網路編程 —— 非阻塞式編程

    執行緒阻塞概述 在生活中,最常見的阻塞現象是公路上汽車的堵塞。汽車在公路上快速行駛,如果前方交通受阻,就只好停下來等待,等到公路順暢,才能恢復行駛。 執行緒在運行中也會因為某些原因而阻塞。所有處于阻塞狀態的執行緒的共同特征:放棄 CPU,暫停運行,只有等到導致阻塞的原因消除,才能恢復運行,或者被其他執行緒中 ......

    uj5u.com 2023-05-15 07:34:46 more
  • Java的執行緒

    介紹執行緒 執行緒是系統調度的最小單元,一個行程可以包含多個執行緒,執行緒是負責執行二進制指令的。 每個執行緒有自己的程式計數器、堆疊(Stack)、暫存器(Register)、本地存盤(Thread Local)等,但是會和行程內其他執行緒共享檔案描述符、虛擬地址空間等。 對于任何一個行程來講,即便我們沒有主動 ......

    uj5u.com 2023-05-15 07:29:29 more
  • 如何優雅得關閉協程呢

    1.簡介 本文將介紹首先為什么需要主動關閉goroutine,并介紹如何在Go語言中關閉goroutine的常見套路,包括傳遞終止信號和協程內部捕捉終止信號。之后,文章列舉了需要主動關閉協程運行的常見場景,如啟動一個協程執行一個不斷重復的任務。希望通過本文的介紹,讀者能夠掌握如何在適當的時候關閉go ......

    uj5u.com 2023-05-15 07:24:22 more
  • 【pandas基礎】--資料整理

    pandas進行資料整理的意義在于,它是資料分析、資料科學和機器學習的前置步驟。 通過資料整理可以提前了解資料的概要,缺失值、重復值等情況,為后續的分析和建模提供更為可靠的資料基礎。 本篇主要介紹利用pandas進行資料整理的各種方法。 1. 資料概要 獲取資料概要資訊可以幫助我們了解資料的基本情況 ......

    uj5u.com 2023-05-15 07:24:17 more
  • 數字分頻器設計(偶數分頻、奇數分頻、小數分頻、半整數分頻、狀態

    偶數分頻:無論是通過D觸發器還是計數器實作,這類分頻都是最容易得到的,并且占空比容易控制在50%。對于D觸發器實作偶數分頻來說,分頻數只能得2^n,其余分頻數只能由計數器法等其他方法實作。除此以外,隨著分頻的數目不斷增大,通過D觸發器實作觸發器數目會增多,在電路設計的程序中應當考慮面積因素。對于計數... ......

    uj5u.com 2023-05-15 07:24:09 more
  • 數字分頻器設計(偶數分頻、奇數分頻、小數分頻、半整數分頻、狀態

    偶數分頻:無論是通過D觸發器還是計數器實作,這類分頻都是最容易得到的,并且占空比容易控制在50%。對于D觸發器實作偶數分頻來說,分頻數只能得2^n,其余分頻數只能由計數器法等其他方法實作。除此以外,隨著分頻的數目不斷增大,通過D觸發器實作觸發器數目會增多,在電路設計的程序中應當考慮面積因素。對于計數... ......

    uj5u.com 2023-05-15 07:22:13 more
  • R語言資料繪圖學習(0x01)-安裝ggplot2與嘗試

    0x01 安裝與R基礎 一直聽說資料分析里R語言是比較‘正統’,況且久聞ggplot2這些R語言的資料分析庫大名,想到今后資料分析和整理的需要,這里開一個簡單的系列學習一些R語言和ggplot2的繪圖基礎。本人學習的書籍是Winston Chang大佬的《R Graphics Cookbook》,且 ......

    uj5u.com 2023-05-13 07:41:55 more
  • python高級技術(行程二)

    一 行程物件及其他方法 '''一臺計算機上面運行著很多行程,那么計算機是如何區分并管理這些行程服務端的呢?計算機會給每一個運行的行程分配一個PID號如何查看 windows電腦 進入cmd輸入tasklist即可查看 tasklist|findstr PID查看具體的行程 linux電腦 進入終端之 ......

    uj5u.com 2023-05-13 07:36:35 more
  • 使用 IDEA 時突然斷電導致 git 本地分支損壞的解決方案

    使用IDEA提交專案的時候突然斷電,重啟后專案 git 損壞,所有檔案變成了 untracked,IDEA 界面上表示為所有檔案名變成綠色,并且無法 pull (也可能是無法 push) 提示 Git Pull Failed From http://***************** * branc ......

    uj5u.com 2023-05-13 07:24:33 more
  • Spring AOP 分享

    初級篇 AOP是什么? Aspect-oriented Programming (AOP) 即面向切面編程。簡單來說,AOP 是一種編程范式,允許我們模塊化地定義橫跨多個物件的行為。AOP 可以幫助我們將應用程式的關注點分離,使得代碼更加清晰、易于維護和擴展。 大白話:在方法執行前后運行指定代碼,比 ......

    uj5u.com 2023-05-13 07:22:03 more