Java NIO:選擇器
阿新 • • 發佈:2020-10-18
最近打算把Java網路程式設計相關的知識深入一下(IO、NIO、Socket程式設計、Netty)
Java NIO主要需要理解緩衝區、通道、選擇器三個核心概念,作為對Java I/O的補充, 以提升大批量資料傳輸的效率。
學習NIO之前最好能有基礎的網路程式設計知識
[Java I/O流](https://www.cnblogs.com/pepper-0611/p/13735018.html)
[Java 網路程式設計](https://www.cnblogs.com/pepper-0611/p/13767359.html)
[Java NIO:緩衝區](https://www.cnblogs.com/pepper-0611/p/13767775.html)
[Java NIO:通道](https://www.cnblogs.com/pepper-0611/p/13797573.html)
傳統監控多個Socket的Java解決方案是為每一個Socket建立一個執行緒並使執行緒阻塞在read呼叫處, 直到資料可讀。這種方式在系統併發不高時可以正常執行,如果是併發很高的系統就需要建立很多的執行緒(每個連線需要一個執行緒)。過多的執行緒會導致頻繁的上下文切換、且執行緒是系統資源,可建立最大執行緒數是有限制的且遠小於可以建立的網路連線數。
NIO的選擇器就是為了解決這個問題, 選擇器提供了同時詢問多個通道是否準備好執行I/O的能力,比如SocketChannel物件是否還有更多的位元組待讀取, ServerSocketChannel是否有已經到達的客戶端連線。
通過使用選擇器,我們可以在一個執行緒裡監聽多個通道的就緒狀態!
## 核心概念
選擇器 :管理可選擇通道集合&更新可選擇通道的就緒狀態
可選擇通道:所有繼承了SelectableChannel的通道, Socket都是可選擇的,而檔案通道不是,只有可選擇通道可以註冊到選擇器上。
選擇鍵:可選擇通道註冊到選擇器後返回選擇鍵, 所以選擇鍵其實是通道與選擇器註冊關係的一個封裝
三者之間的關係:***可選擇通道***註冊到***選擇器***上,返回***選擇鍵***
## 選擇器使用
使用選擇器的步驟一般是:
1. 構造選擇器
2. 可選擇通道註冊到選擇器
3. 選擇器選擇(選擇出就緒通道)
4. 對就緒通道進行讀寫操作
5. 重複 2 ~ 4
下面從這幾步進行講解
### 構造選擇器
使用靜態工廠方法構造(底層使用SelectorProvider建立Selector例項, SelectorProvider支援java spi擴充套件)
```java
Selector selector = Selector.open();
```
### 可選擇通道註冊到選擇器
只有執行在**非阻塞模式**下的通道可以註冊到選擇器上
註冊的方法定義在SelectableChannel類中, 註冊時需帶上可選擇的操作(四種可選擇操作,定義在SelectionKey中),也可以帶上附件
註冊成功返回選擇鍵,具體API如下:
```java
//引數 選擇器 + 可選擇操作
public final SelectionKey register(Selector sel, int ops)
//帶附件的版本
public abstract SelectionKey register(Selector sel, int ops, Object att)
```
### 選擇器選擇
select方法選擇出就緒的通道,把該就緒通道關聯的SelectionKey放到選擇器的selectedKeys集合中(通道就緒指底層Socket已經就緒,執行連線或者讀寫操作時不會阻塞)
### 對就緒通道進行讀寫操作
對就緒通道的讀寫操作見下面Demo
### Demo
寫了一個demo把上面幾步整合起來,程式碼主要兩部分:可選擇通道註冊&選擇器選擇 和 對就緒通道進行讀寫操作
#### 可選擇通道註冊&選擇器選擇
```java
//構造選擇器
Selector selector = Selector.open();
//初始化ServerSocketChannel繫結本地埠並設定為非阻塞模式
ServerSocketChannel ch = ServerSocketChannel.open();
ch.bind(new InetSocketAddress("127.0.0.1", 7001));
ch.configureBlocking(false);
//通道註冊到選擇器,並且關心ACCEPT操作(因為是Server)
ch.register(selector, SelectionKey.OP_ACCEPT);
//一直迴圈, 進行通道就緒狀態的選擇
while (true) {
int n = selector.select();
if (n == 0) {
continue;
}