1. 程式人生 > >Java的中BIO、NIO、AIO-2

Java的中BIO、NIO、AIO-2

bili 成了 讀寫操作 個數 _for soc 傳統 輸出流 微軟雅黑

Java的中BIO、NIO、AIO-2

java

舉個栗子

接上一篇接著說,C/S模式、Reactor模式、Proactor模式是服務器處理IO常用的處理模型,這一篇就來解釋一下這幾種模式:
以一個餐飲為例,每一個人來就餐就是一個事件,他會先看一下菜單,然後點餐。就像一個網站會有很多的請求,要求服務器做一些事情。處理這些就餐事件的就需要我們的服務人員了。

在多線程處理的方式會是這樣的:

一個人來就餐,一個服務員去服務,然後客人會看菜單,點菜。 服務員將菜單給後廚。

二個人來就餐,二個服務員去服務……

五個人來就餐,五個服務員去服務……

這個就是多線程的處理方式,一個事件到來,就會有一個線程服務。很顯然這種方式在人少的情況下會有很好的用戶體驗,每個客人都感覺自己是VIP,專人服務的。如果餐廳一直這樣同一時間最多來5個客人,這家餐廳是可以很好的服務下去的。(這樣就很熟悉了,這既是最簡單來一個Socket連接,就創建一個線程。)

但是越來越多的人對這家餐廳滿意,客源又多了,同時來吃飯的人到了20人,老板高興不起來了,再請服務員吧,占地方不說,還要開工錢,再請人就攢不到錢了。怎麽辦呢?老板想了想,10個服務員對付20個客人也是能對付過來的,服務員勤快點就好了,伺候完一個客人馬上伺候另外一個,還是來得及的。綜合考慮了一下,老板決定就使用10個服務人員的線程池啦。(創建固定數量的線程池,建一個任務隊列,使用多線程的方式對任務進行處理)
但是如果人數太多,那麽10個人的服務員根本忙不過來,同樣,沒有人的時候,10個人的服務員在閑等著又造成了很大的資源浪費。

老板後來發現,客人點菜比較慢,大部服務員都在等著客人點菜,其實幹的活不是太多。老板能當老板當然有點不一樣的地方,終於發現了一個新的方法,那就是:當客人點菜的時候,服務員就可以去招呼其他客人了,等客人點好了菜,直接招呼一聲“服務員”,馬上就有個服務員過去服務。老板決定改變經營模式。(這就是Reactor模式!!)

後面老板做出了名聲,有錢了,萌生出一個新的想法,做外賣。用戶打電話告訴接單員他的訂單,然後接單員把訂單發給餐飲部門,接單員可以繼續工作接單,等餐飲部門把訂單做好的時候,告訴接單員訂單完成來取飯,接單員根據訂單信息把飯菜交給外賣小哥送出去。(這就是Proactor模式!!)

前面是舉例子說明這幾種模式,可能這樣理解起來很懵,下面就每種模式詳細的說明一下:

1. BIO中的C/S編程模型

網絡編程的基本模型是C/S模型,也就是兩個進程間的通信。服務端提供IP和監聽端口,客戶端通過向服務器端的端口發起連接請求,通過三次握手如果連接成功,雙方就能進行套接字通信了,這就是TCP連接,當然只是廢話。
在傳統的同步阻塞開發模型中,服務器ServerSocket負責綁定IP地址,啟動監聽端口;Socket負責發起連接,連接成功之後,雙方通過輸入輸出流進行同步阻塞式通信。采用BIO通信模型的服務器端,通常有一個獨立的線程負責監聽客戶端的連接,它接收到客戶端的請求連接之後會為每一個客戶端創建一個線程進行鏈路的連接,連接結束之後銷毀線程。看下圖示例:

技術分享圖片
BIO通信模型

該模型最大的缺點是缺乏彈性伸縮能力,服務端線程個數和客戶端的並發訪問數是1:1的關系,當客戶端的數目過多的時候,服務器可能承受不住,頻繁的創建、銷毀線程也很消耗系統資源。

2.偽裝異步編程模型

一對一的TCP連接服務太浪費資源,為了改進這一點,可以使用線程池來管理這些線程,為什麽是偽異步呢,因為其實對於線程池中線程而言還是一對一的服務這點並沒有什麽改變。使用線程池知識能夠對資源實現有效的管控和約束。

技術分享圖片
偽異步IO編程模型

當線程池中的數目有限的時候,如果有大量的並發請求,超過最大線程使用數目之後只能進行等待,知道線程池中有空閑的線程可以使用,同時線程對socket的處理模型還是采用阻塞的方式進行處理。

3.Reactor模式

技術分享圖片
Reactor模式

Reactor模式用於同步IO。開發者開始的時候要把自己感興趣的時間註冊到事件分離器,並提供相應的處理函數。時間分離器等待某個事件可應用或者某個操作的狀態發生改變(比如文件描述符可讀寫、或者是Socket可讀寫),事件分離器會把這個事件傳給時間註冊的事件處理器或者回調函數,由其來完成實際的讀寫操作。下面以讀操作為例來說明一下Reactor的具體步驟:

  1. 程序註冊讀就緒事件和相關聯的處理器
  2. 事件分離器等待事件的發生
  3. 當發生讀就緒事件的時候,事件分離器調用第一步註冊的事件處理器
  4. 事件處理器執行實際的讀取操作。如果需要,它可以再次的宣稱對這個讀就緒事件感興趣,重復上面的步驟。

覺得這樣說明起來其實很虛,第三篇將會演示具體的代碼。

4.Proactor模式

技術分享圖片
Proactor模式

Proactor模式用於異步IO。它其實和reactor模式很相似,也是需要先註冊事件到時間分離器,並提供相應的處理函數。但是這個事件不是就緒事件,而是直接發起一個異步的讀寫操作(請求)事件,但是實際的工作有操作系統完成。在發起事件的時候,需要的提供的參數包括用於存放數據的緩存區,以及這個請求完成之後的回調函數等信息。事件分離器等待這個事件的完成,然後轉發完成後的事件給相應的處理函數或者回調等。
下面來以讀事件為例說明一下該模式的具體步驟:

  1. 應用程序初始化一個異步讀取操作,然後註冊相應的事件處理器,此時事件處理器不關註讀取就緒事件,而是關註讀取完成事件,這是區別於Reactor的關鍵。
  2. 事件分離器等待讀取操作完成事件
  3. 在事件分離器等待讀取操作完成的時候,操作系統調用內核線程完成讀取操作,並將讀取的內容放入用戶傳遞過來的緩存區中。這也是區別於Reactor的一點,Proactor中,應用程序需要傳遞緩存區。
  4. 事件分離器捕獲到讀取完成事件後,激活應用程序註冊的事件處理器,事件處理器直接從緩存區讀取數據,而不需要進行實際的讀取操作。如果有需要還可以繼續註冊事件到事件處理器,重復上面的步驟。

簡單總結一下,Reactor和Proactor的異同:

  • 他們相同的地方框架大致相同,程序都需要註冊相應的時間到事件分離器,事件分離器根據狀態的變化來告知相應的事件處理器。都是事件驅動型的IO通信復用模型。
  • 他們不同的地方,關註的事件不一樣,Reactor模式關註的是時間的就緒狀態,Proactor模式關註的是事件的完成狀態。Reactor模式的程序自己讀取或者寫入數據,而Proactor模式中,應用程序不需要進行實際的讀寫過程,它只需要從緩存區讀取或者寫入即可,操作系統會讀取緩存區或者寫入緩存區到真正的IO設備(Proactor需要操作系統提供完善的異步操作的API接口)。

所以總的來說,同步和異步是相對於應用和內核的交互方式而言的,同步 需要主動去詢問,而異步的時候內核在IO事件發生的時候通知應用程序,而阻塞和非阻塞僅僅是系統在調用系統調用的時候函數的實現方式而已。

參考資料

  • https://blog.csdn.net/caiwenfeng_for_23/article/details/8458299 reactor和proactor模式
  • http://daimojingdeyu.iteye.com/blog/828696 Reactor模式,或者叫反應器模式
  • http://xmuzyq.iteye.com/blog/783218 高性能IO設計的Reactor和Proactor模式
  • https://www.cnblogs.com/charlesblc/p/6072827.html 高性能IO設計 & Java NIO & 同步/異步 阻塞/非阻塞 Reactor/Proactor
  • https://www.cnblogs.com/charlesblc/p/6072827.html NIO學習
  • http://www.cnblogs.com/fanzhidongyzby/p/4098546.html 高性能IO模型淺析 (推薦)
  • https://blog.csdn.net/anxpp/article/details/51512200#t3 Java 網絡IO編程總結(BIO、NIO、AIO均含完整實例代碼)(推薦)
  • https://blog.csdn.net/anxpp/article/details/51503329 Linux 網絡 I/O 模型簡介(圖文)(推薦)
  • http://blog.jobbole.com/59676/ 兩種高性能I/O設計模式(Reactor/Proactor)的比較

Java的中BIO、NIO、AIO-2