1. 程式人生 > >Java IO:網路IO模型

Java IO:網路IO模型

網路IO模型有5種,分別為:阻塞式IO,非阻塞式IO,IO複用,訊號驅動式IO 和 非同步IO

一.  作業系統如何處理IO

    Linux 會把所有的外部裝置都看成一個檔案來操作,對外部裝置的操作可以看成是對檔案的操作。我們對一個檔案的讀寫,都會通過核心提供的系統呼叫,核心會給我們返回一個 File Descriptor,這個描述符是一個數字,指向核心的一個結構體,我們應用程式對檔案的讀寫就是對描述符指向的結構體的讀寫

    系統呼叫是如何完成IO操作?Linux 會把記憶體分為 核心區和使用者區。Linux 的核心區會幫我們管理所有的硬體資源,並且會提供系統呼叫,我們應用程式的讀操作,就會通過系統呼叫 read 發起一個讀操作,這個時候,核心就會建立一個檔案描述符,通過驅動向硬體傳送讀指令,並且把讀的資料放在描述符指向的結構體的緩衝區中

。當這個資料傳到使用者區的時候,就完成了一次 IO。

    Linux 系統呼叫的 read,是一個阻塞函式。這個我們應用程式在發起read系統呼叫的時候,就必須要阻塞,程序掛起,等待檔案描述符的讀就緒。

    從上面我們可以知道,應用程式的一個 read系統呼叫,需要經過:

  1. 硬體讀取檔案資料到檔案描述符指向的結構體的緩衝區。
  2. 結構體的緩衝區的資料 傳輸到 使用者區。

二. 阻塞式 IO

    在進行網路IO的時候,程序發起一個 recvform 系統呼叫,然後程序就會被阻塞,什麼也不幹,等待上述 所說的 1 和 2步驟完成(也就是硬體讀取內容到核心的結構體緩衝區,結構體緩衝區的內容到使用者區),最後程序再被處理。大致如下圖1:


圖1

二. 非阻塞式 IO

    在上述的 阻塞式 IO中,可以看到,程序在發起了 recvform 系統呼叫的時候,就會阻塞,那麼非阻塞式 IO 和 阻塞式 IO 的一個區別就在這裡,它並不會阻塞,而是馬上返回一個錯誤碼,程序在得到錯誤碼之後,可以乾點別的事情,然後再重複上述的步驟,也就是又發起一個 recvform 系統呼叫。這個過程就成為輪詢

    需要注意的是,上述的輪詢只是針對作業系統處理IO的第一步,也就是 硬體讀取資料到核心的結構體緩衝區,不針對第二步,也就是說,核心的結構體緩衝區的資料複製到使用者區還是需要經過阻塞。如下圖2:


圖2

三. IO複用

    由上面的非阻塞式IO可以看出,輪詢佔了很大一部分過程,這個過程會消耗很多CPU時間。這個輪詢是由使用者區發起的,但是,這個輪詢如果是由核心區發起的,那麼效率就有提升。IO複用提供了兩個 系統呼叫 :select 和 poll

。select 系統呼叫是核心級別的,它和 非阻塞式的輪詢 有一個區別,就是select 輪詢 可以等待多個 socket,任何一個socket 的資料準備好了,那麼就可以返回進行讀。 select 和 poll呼叫之後,會阻塞程序,那麼這種阻塞程序不同於 第一種 阻塞式IO的阻塞,此時的select不是等到socket資料全部到達後再處理,而是有一部分資料就會呼叫使用者程序處理。打個比方:釣魚的時候,僱傭一個幫手,他可以同事拋下多個魚竿,任何一杆的魚上鉤,他就會拉桿,他只負責幫我們,不負責幫我們處理,所有我們還得在一邊等著,等到他拉桿,我們再處理。 如下圖3:


圖3

Ps:實際上 select/poll 是落後的,因為 select 的控制代碼數有限,為1024 個,也就是說,同次檢測 1025 個控制代碼死 是不可能。另外,核心的select是採用輪詢的方法,select檢測的控制代碼數越大就會越耗時,這些都是問題。那麼epoll就能夠解決這些問題,epoll 實際上也是 slect/poll 的增強版。

四. 訊號驅動式IO

    也就是說,資料未準備就緒的時候,那麼就進行等待,但是這個等待不會進行輪詢,也不會阻塞。而是在某一時刻如果資料準備好了,那麼核心會通知程序啟動IO操作,將資料從核心區複製到使用者區。如下圖4 所示:


圖4

五. 非同步IO

    非同步 IO 是指 相當於 訊號式驅動 IO 的一個升級版本。非同步 IO 是等 核心完成整個操作之後再通過應用程式,這整個過程包括了 硬體讀取資料到 核心結構體的緩衝區中,以及緩衝區的資料複製到 使用者區中,這整個過程程序都不會阻塞。如下圖5:


圖5

總結:以上就是 作業系統如何處理IO,以及常用的 5種 網路 IO模型。包括(阻塞式IO(java的傳統IO),非阻塞式IO(NIO),多路複用IO,訊號驅動IO,非同步IO)

參考: