BIO、NIO、AIO通訊機制

1、BIO的理解

首先咱們經過通訊模型圖來熟悉下BIO的服務端通訊模型:採用BIO通訊模型的服務端,一般由一個獨立的Acceptor線程負責監聽客戶端的鏈接,它接收到客戶端的鏈接請求以後爲每一個客戶端建立一個新的線程進行鏈路處理,處理完成以後,經過輸出流返回應答給客戶端,線程銷燬。這就是典型的一請求一應答通訊模型。這個是在多線程狀況下執行的。當在單線程環境下時,在while循環中服務端會調用accept方法等待接收客戶端的鏈接請求,一旦接收到一個鏈接請求,就能夠創建socket,並在該socket上進行讀寫操做,此時不能再接收其它客戶端的鏈接請求,只能等待同當前鏈接的客戶端的操做執行完成。 
編程

 

該模型最大的問題就是缺少彈性伸縮能力,當客戶端併發訪問量增長後,服務端的線程個數和客戶端併發訪問數呈1:1的正比關係,因爲線程是Java虛擬機很是寶貴的系統資源,當線程數膨脹以後,系統的性能將急劇降低,隨着併發訪問量的繼續增大,系統會發生線程堆棧溢出、建立新線程失敗等問題,並最終致使進程宕機或者僵死,不能對外提供服務。後端

 

2、僞異步I/O編程

爲了解決同步阻塞I/O面臨的一個鏈路須要一個線程處理的問題,後來有人對它的線程模型進行了優化,後端經過一個線程池來處理多個客戶端的請求接入,造成客戶端個數M:線程池最大線程數N的比例關係,其中M能夠遠遠大於N,經過線程池能夠靈活的調配線程資源。設置線程的最大值,防止因爲海量併發接入致使線程耗盡。 
採用線程池和任務隊列能夠實現一種叫作僞異步的I/O通訊框架。模型圖以下。 
數組

 當有新的客戶端接入時,將客戶端的Socket封裝成一個Task(該任務實現Java.lang.Runnablle接口)投遞到後端的線程池中進行處理,JDK的線程池維護一個消息隊列和N個活躍線程對消息隊列中的任務進行處理。因爲線程池能夠設置消息隊列的大小和最大線程數,所以,它的資源佔用是可控的,不管多少個客戶端併發訪問,都不會致使資源的耗盡和宕機。 服務器

因爲線程池和消息隊列都是有界的,所以,不管客戶端併發鏈接數多大,它都不會致使線程個數過於膨脹或者內存溢出,相對於傳統的一鏈接一線程模型,是一種改良。 
僞異步I/O通訊框架採用了線程池實現,所以避免了爲每一個請求都建立一個獨立線程形成的線程資源耗盡問題。可是因爲它底層的通訊依然採用同步阻塞模型,所以沒法從根本上解決問題。 
經過對輸入和輸出流的API文檔進行分析,咱們瞭解到讀和寫操做都是同步阻塞的,阻塞的時間取決於對方IO線程的處理速度和網絡IO的傳輸速度,本質上講,咱們沒法保證生產環境的網絡情況和對端的應用程序能足夠快,若是咱們的應用程序依賴對方的處理速度,它的可靠性就會很是差。網絡

 

3、NIO編程(非阻塞IO)

與Socket類和ServerSocket類相對應,NIO也提供了SocketChannel和ServerSocketChannel兩種不一樣的套接字通道實現,在JDK1.4中引入。這兩種新增的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用很是簡單,可是性能和可靠性都很差,非阻塞模式則正好相反。咱們能夠根據本身的需求來選擇合適的模式,通常來講,低負載、低併發的應用程序能夠選擇同步阻塞IO以下降編程複雜度,可是對於高負載、高併發的網絡應用,須要使用NIO的非阻塞模式進行開發。 
首先來了解一些概念 多線程

(1)緩衝區Buffer 
Buffer是一個對象,它包含一些要寫入或者要讀出的數據,在NIO庫中,全部數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的;在寫入數據時,寫入到緩衝區中,任什麼時候候訪問NIO中的數據,都是經過緩衝區進行操做。 
緩衝區實質上是一個數組。一般它是一個字節數組(ByteBuffer),也可使用其餘種類的數組,可是一個緩衝區不只僅是一個數組,緩衝區提供了對數據的結構化訪問以及維護讀寫位置(limit)等信息。經常使用的有ByteBuffer,其它還有CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 併發

 

(2)通道Channel 
Channel是一個通道,能夠經過它讀取和寫入數據,它就像自來水管同樣,網絡數據經過Channel讀取和寫入。通道與流的不一樣之處在於通道是雙向的,流只是一個方向上移動(一個流必須是InputStream或者OutputStream的子類),並且通道能夠用於讀、寫或者用於讀寫。同時Channel是全雙工的,所以它能夠比流更好的映射底層操做系統的API。特別是在Unix網絡編程中,底層操做系統的通道都是全雙工的,同時支持讀寫操做。咱們經常使用到的ServerSocketChannnel和SocketChannel都是SelectableChannel的子類。 框架


(3)多路複用器Selector 
多路複用器Selector是Java NIO編程的基礎,多路複用器提供選擇已經就緒的任務的能力,簡單的說,Selector會不斷的輪詢註冊在其上的Channel,若是某個Channel上面有新的TCP鏈接接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,而後經過SelectionKey能夠獲取就緒Channel的集合,進行後續的I/O操做。異步

 
一個多用複用器Selector能夠同時輪詢多個Channel,因爲JDK使用了epoll()代替傳統的select實現,因此它並無最大鏈接句柄1024/2048的限制,這也意味着只須要一個線程負責Selector的輪詢,就能夠接入成千上萬的客戶端。 
socket

 


儘管NIO編程難度確實比同步阻塞BIO大不少,可是咱們要考慮到它的優勢: 

(1)客戶端發起的鏈接操做是異步的,能夠經過在多路複用器註冊OP_CONNECT等後續結果,不須要像以前的客戶端那樣被同步阻塞。 

(2)SocketChannel的讀寫操做都是異步的,若是沒有可讀寫的數據它不會同步等待,直接返回,這樣IO通訊線程就能夠處理其它的鏈路,不須要同步等待這個鏈路可用。 

(3)線程模型的優化:因爲JDK的Selector在Linux等主流操做系統上經過epoll實現,它沒有鏈接句柄數的限制(只受限於操做系統的最大句柄數或者對單個進程的句柄限制),這意味着一個Selector線程能夠同時處理成千上萬個客戶端鏈接,並且性能不會隨着客戶端的增長而線性降低,所以,它很是適合作高性能、高負載的網絡服務器。

  

4、AIO(異步非阻塞IO)

JDK1.7升級了NIO類庫,升級後的NIO類庫被稱爲NIO2.0。也就是咱們要介紹的AIO。NIO2.0引入了新的異步通道的概念,並提供了異步文件通道和異步套接字通道的實現。異步通道提供兩種方式獲取操做結果。 

(1) 經過Java.util.concurrent.Future類來表示異步操做的結果;

(2) 在執行異步操做的時候傳入一個Java.nio.channels. 


CompletionHandler接口的實現類做爲操做完成的回調。 

NIO2.0的異步套接字通道是真正的異步非阻塞IO,它對應UNIX網絡編程中的事件驅動IO(AIO),它不須要經過多路複用器(Selector)對註冊的通道進行輪詢操做便可實現異步讀寫,從而簡化了NIO的編程模型。 

咱們能夠得出結論:異步Socket Channel是被動執行對象,咱們不須要像NIO編程那樣建立一個獨立的IO線程來處理讀寫操做。對於AsynchronousServerSocketChannel和AsynchronousSocketChannel,它們都由JDK底層的線程池負責回調並驅動讀寫操做。正由於如此,基於NIO2.0新的異步非阻塞Channel進行編程比NIO編程更爲簡單。

 

總結:

由上述總結得出,並不意味着全部的Java網絡編程都必需要選擇NIO和Netty,具體選擇什麼樣的IO模型或者NIO框架,徹底基於業務的實際應用場景和性能訴求,若是客戶端併發鏈接數很少,周邊對接的網元很少,服務器的負載也不重,那就徹底不必選擇NIO作服務端;若是是相反狀況,那就考慮選擇合適的NIO框架進行開發。

相關文章
相關標籤/搜索