java-nio Selector & SelectionKey

1. 概述

說(shuō)明一下:該文章并不是啥拿來(lái)就用的文章,而是一篇幫助理解這些組件關(guān)系的文章,因?yàn)?,我在使用這些組件的時(shí)候就一直困惑他們之間的關(guān)系。我就用一篇文章總結(jié)一下我梳理的結(jié)果

java NIO中文為非阻塞IO,其中非阻塞的實(shí)現(xiàn)最主要就依賴于我們標(biāo)題中提到的這個(gè)組件:Selector,本文就將詳細(xì)介紹一下這個(gè)組件及其配套設(shè)施。

在介紹之前,我先對(duì)這個(gè)組件的作用做一個(gè)總結(jié):Selector通過(guò)系統(tǒng)調(diào)用,獲取操作系統(tǒng)中的就緒事件,selector定義了四種四件:OP_ACCEPT、OP_CONNECT、OP_READ、OP_WRITE

一旦某個(gè)事件就緒了,就可以做相應(yīng)的處理,比如一個(gè)連接剛過(guò)來(lái),會(huì)觸發(fā)connect,然后觸發(fā)accapt,我們都可以為這些事件提供相應(yīng)的處理。這樣相對(duì)于原來(lái)的阻塞IO來(lái)說(shuō),可以不影響代碼向下執(zhí)行,通過(guò)不斷輪詢事件的方式,起到了非阻塞的作用。

2. Selector組件介紹

首先,看看這個(gè)Selector組件,該組件是NIO的核心組件。selector是一個(gè)查詢系統(tǒng)就緒事件的組件,配套在Channel中使用,可以讓多個(gè)Channel注冊(cè)到某個(gè)selector中,然后通過(guò)select方法,可以查詢就緒事件。

2.1 創(chuàng)建與關(guān)閉

調(diào)用Selector的靜態(tài)方法open通過(guò)系統(tǒng)提供的 selector provider得到selector對(duì)象,通過(guò) close方法關(guān)閉

2.2 使用介紹

在知道了創(chuàng)建以后,那么就要了解這個(gè)組件是如何使用的,首先,我們創(chuàng)建任意一個(gè)channel

注意:為了代碼整潔,下面的代碼我都去掉了異常處理模塊

ServerSocketChannel server = new ServerSocketChannel(new INetAddress("127.0.0.1", 8080));

有了channel以后,我們要將channel注冊(cè)到selector中

Selector selector = Selector.open();
SelectionKey key = server.register(selector, SelectionKey.OP_ACCAPT);

這里,我們將channel注冊(cè)到了selector中,并添加了一個(gè)ACCAPT事件,這個(gè)表示該channel對(duì)accapt事件感興趣,需要selector監(jiān)聽系統(tǒng)是否accapt就緒。

事件已經(jīng)注冊(cè)好了,那么就開始讓selector開始工作吧?。?!

selector.select();
for (Iterator<SelectionKey> it = selector.selectedKeys().iterator(); it.hasNext(); ) {
  SelectionKey key = it.next();
  if(key.isAccaptable()){
      // doSomething for accapt
  }
}

通過(guò)selector調(diào)用select方法,向系統(tǒng)查詢一次事件就緒情況,并將就緒情況封裝成SelectionKey,關(guān)于SelectionKey的內(nèi)容可以參考下文中的內(nèi)容。

上面是selector的一個(gè)基本使用,那么如果又出現(xiàn)新問(wèn)題:我們一開始僅僅注冊(cè)了accapt事件,那么,假如我們現(xiàn)在要系統(tǒng)完成read的功能,而我們注冊(cè)的時(shí)候又沒寫,怎么處理呢?

我這里不直接寫代碼,大家可以跟著我的這個(gè)思路來(lái)理解一下這幾個(gè)組件之間的關(guān)系:我們知道selector是一個(gè)組件,該組件本身是沒任何感興趣的事件的,換句話說(shuō),selector只是工具,他就負(fù)責(zé)查詢而已,那還有什么能修改感興趣的事件呢?我們這時(shí)的目光也許定格在了這個(gè)channel,channel注冊(cè)到selector以后,并添加了感興趣的事件,那么channel理應(yīng)提供了一個(gè)保存interest事件的隊(duì)列,

image.png

通過(guò)讀API文檔, 發(fā)現(xiàn)并沒有這個(gè)方法提供。其實(shí)也能夠理解:因?yàn)閟elector是組件,channel是組件,獨(dú)立的channel組件怎么能為select組件的功能提供方法呢?所以,提供方法的一定是他們的中間產(chǎn)物——SelectionKey,我們知道channel注冊(cè)進(jìn)selector的時(shí)候就可以獲取一個(gè)SelectionKey

// 使用該方法就可以更改感興趣的事件
// key 在上文代碼就是SelectionKey的一個(gè)實(shí)例
key.interestOps(int ops)
// 下面介紹這個(gè)方法兩個(gè)常見的使用
// 在原感興趣的事件基礎(chǔ)上增加READ事件
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
// 在原感興趣的事件基礎(chǔ)上取消READ事件
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ)

2.3 Selector組件介紹

上文已經(jīng)介紹了如何使用selector組件,通過(guò)上面我們已經(jīng)知道了selector的基本使用。那么現(xiàn)在我們看看該組件

2.3.1 select() 操作

select操作是該組件的核心。通過(guò)該操作,應(yīng)用程序會(huì)向操作系統(tǒng)為每一個(gè)注冊(cè)到該selector的channel查詢他們感興趣的事件是否準(zhǔn)備就緒。

  • select操作分兩大類:
  1. 調(diào)用 select(), select(long), 和 selectNow()方法。我們知道select操作事實(shí)上是阻塞式的,通過(guò)限定時(shí)間的方式,可以讓select在一定時(shí)間以后就跳出,而不會(huì)一直處在阻塞狀態(tài)。select操作以后產(chǎn)生的效果就是將已經(jīng)就緒的SelectionKey加入到其內(nèi)部的SelectedSet中。

  2. 調(diào)用select(Consumer), select(Consumer,long), 和 selectNow(Consumer)不會(huì)將已經(jīng)就緒的操作放進(jìn)selectedKey中,而是通過(guò)Consumer將這個(gè)key消費(fèi)掉。

  • select操作通過(guò)以下三步:
  1. 首先會(huì)刪除任何一個(gè)在selector的Cancelled_Set中的SelectionKey,并將其channel注銷。這個(gè)過(guò)程以后就清空Cancelled_Set
  2. 系統(tǒng)再根據(jù)每個(gè)channel感興趣的事件,并向系統(tǒng)詢問(wèn)就緒情況,然后生產(chǎn)SelectionKey。這里也有兩個(gè)情況

情況一:如果某個(gè)channel的SelectionKey并不在selector的SelectedSet中,然后通過(guò)這個(gè)操作,將該selectionKey加入到SelectedSet中,而之前在SelectedSet中的內(nèi)容將被遺棄

情況二:某個(gè)channel的SelectionKey已經(jīng)在selector的SelectedSet中,那么該內(nèi)容將保存在set中

  1. 如果這些環(huán)節(jié)中向Cancel_Set中添加了內(nèi)容,那么會(huì)按照步驟一繼續(xù)執(zhí)行
  • selector內(nèi)部

通過(guò)閱讀select方法的執(zhí)行流程,我們可以看到幾個(gè)重要的結(jié)構(gòu):Selected_Set, Cancelled_Set,事實(shí)上,selector中還有要給包含兩者的全集set,一共三個(gè)set

Selected_Set 存儲(chǔ)著channel感興趣的且已經(jīng)就緒的SelectionKey

Cancelled_Set存儲(chǔ)著不要的SelectionKey,比如下次select操作的時(shí)候,我們可以看步驟一,會(huì)將與之相關(guān)的內(nèi)容清空。

image.png

3. SelectionKey介紹

通過(guò)對(duì)Selector的介紹,我們也大概明白了這個(gè)SelectionKey是什么,其實(shí)這就是連接selector組件與channel的中間產(chǎn)物。

3.1 內(nèi)部結(jié)構(gòu)

我們一開始在分析如何修改感興趣事件的時(shí)候就提到過(guò)這個(gè)中間產(chǎn)物,那么我們具體來(lái)看看這個(gè)結(jié)構(gòu)。

該組件包含兩個(gè)set:

  1. interest_set表示對(duì)于注冊(cè)進(jìn)selector的channel所感興趣的事件

  2. ready_set存儲(chǔ)了那些select以后就緒的操作。該操作不能直接更新,而是在selector執(zhí)行select操作期間會(huì)更新

注意了interest_set僅僅儲(chǔ)存著channel感興趣的事件,而這些事件就緒其實(shí)是在ready_set中,select更新的也是ready_set中的值,而ready_set中的內(nèi)容又必須是在interest_set中的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容