天天看點

Java IO學習筆記六:NIO到多路複用

作者:Grey

原文位址:Java IO學習筆記六:NIO到多路複用

雖然NIO性能上比BIO要好,參考:Java IO學習筆記五:BIO到NIO

但是NIO也有問題,NIO服務端的示例代碼中往往會包括如下代碼:

即:周遊所有的<code>SocketChannel</code>,擷取能讀寫資料的用戶端,當用戶端數量非常多的時候,服務端要輪詢所有連接配接的用戶端拿資料(<code>recv</code>調用),很多調用是無意義的,這樣會導緻頻繁的使用者态切換成核心态,導緻性能變差。

多路複用技術可以解決NIO的這個問題,多個IO通過一個系統調用獲得其中的IO狀态,然後由程式對有狀态的IO進行讀寫操作。在Linux系統中,多路複用的實作有:

基于POSIX标準的SELECT

POLL (select隻支援最大fd &lt; 1024,如果單個程序的檔案句柄數超過1024,select就不能用了。poll在接口上無限制)

EPOLL

其中SELECT和POLL類似,但是有一些差別,參考select和poll的差別

無論NIO,SELECT還是POLL,都是要周遊所有IO,詢問狀态,隻不過周遊這件事到底是核心來做還是應用程式來做而已。

而epoll,可以看成是SELECT和POLL的增強,在調用select/poll時候,都需要把fd集合從使用者态拷貝到核心态,但是epoll調用epoll_ctl時拷貝進核心并儲存,之後每次epoll_wait不做拷貝,而且epoll采用的是事件通知方式,每當fd就緒,系統注冊的回調函數就會被調用,将就緒fd放到rdllist裡面。時間複雜度O(1)。

更多内容可以參考:

深入了解 Epoll

Select、Poll、Epoll詳解

Java的Selector封裝了底層epoll和poll的API,可以通過指定如下參數來調用執行的核心調用, 在Linux平台,如果指定

則底層調用poll,

指定為:

或者不指定,則底層調用epoll。

源碼參考:jdk8u-jdk

Java IO學習筆記六:NIO到多路複用

接下來,我們使用一套服務端代碼,在Linux伺服器上運作,分别指定底層用epoll和poll,并用<code>strace</code>工具來追蹤其核心調用。

準備服務端代碼:

和服務端代碼在同一目錄下準備一個腳本<code>SocketMultiplexingV1.sh</code>

執行

底層調用Poll

重新打開一個控制台,通過nc工具連接配接這個服務端

服務端可以正常接收到連接配接

暫時先不要發送資料,此時,檢視服務端的程序:

檢視服務端目前關聯的檔案描述符

其中<code>4u</code>為服務端監聽的Socket檔案描述符,<code>7u</code>為新連接配接進來的用戶端Socket檔案描述符。

通過nc用戶端給服務端發送一些資料,用戶端也可以正常收到服務端傳回的資料

接下來停掉服務端和用戶端, 檢視追蹤日志

其中Poll.1678為主線程日志, 我們一一看下整個調用過程

以上兩個調用對應了代碼中建立Socket并綁定9090端口進行監聽這個邏輯。

以上調用對應了:

調用的poll方法表示一個新的檔案描述符<code>4u</code>有<code>POLLIN(POLLIN:There is data to read)</code>的事件

這裡說明接收了一個新的Socket連接配接,就是我們剛才用<code>lsof</code>看到的<code>7u</code>這個檔案描述符。,調用了<code>poll</code>方法,說明一個新的檔案描述符<code>7u</code>有<code>POLLIN(POLLIN:There is data to read)</code>的事件。

我們的代碼中對于每次接收的用戶端,也會把用戶端設定為非阻塞,即:

對應的核心調用就是:

以上就是poll調用對應核心函數的調用。

接下來切換成epoll模式,重新執行腳本

用nc連接配接服務端

服務端響應正常

通過nc發送一些資料

也可以正常接收

接下來停掉服務端和用戶端,檢視主線程調用情況

其中建立Socket,Bind 9090端口,設定非阻塞和Poll都是相同的調用

但是一旦有新的連接配接進來

epoll_create: 建立一個epoll執行個體,檔案描述符

epoll_ctl: 将監聽的檔案描述符添加到epoll執行個體中,執行個體代碼為将标準輸入檔案描述符添加到epoll中

epoll_wait: 等待epoll事件從epoll執行個體中發生, 并傳回事件以及對應檔案描述符

調用epoll_create時,核心除了幫我們在epoll檔案系統裡建了個file結點,在核心cache裡建了個紅黑樹用于存儲以後epoll_ctl傳來的socket外,還會再建立一個list連結清單,用于存儲準備就緒的事件。

當epoll_wait調用時,僅僅觀察這個list連結清單裡有沒有資料即可。有資料就傳回,沒有資料就sleep,等到timeout時間到後即使連結清單沒資料也傳回。是以,epoll_wait非常高效。

源碼:Github

參考資料:

select和poll的差別