1. 程式人生 > >java的BIO,NIO,AIO的區別和用法

java的BIO,NIO,AIO的區別和用法

java網路io程式設計,從傳統的BIO(同步阻塞)到NIO(同步非阻塞)再到AIO(非同步非阻塞).

場景:客戶端想服務端傳送請求,服務端會為每個客戶端建立一個執行緒來響應,問題來了,如果客戶端出現了延時等異常,多是網路異常,這樣的話,服務端的為它建立的執行緒,一直處於等待狀態,這樣這個執行緒就會佔用很長時間(因為資料的準備和讀取都在這個執行緒上完成),更糟糕的情況是,如果此時有大量的併發訪問,伺服器就會為之建立大量的執行緒來響應,所以引起伺服器資源枯竭等。

一、傳統的BIO的一些問題,BIO網路程式設計的基本模型是C/S模型,即兩個程序間的通訊.

服務端提供IP和監聽埠,客戶端通過連線操作想服務端監聽的地址發起連線請求,通過三次握手連線,如果連線成功建立,雙方就可以通過套接字進行通訊。

傳統的同步阻塞模型開發中,ServerSocket負責繫結IP地址,啟動監聽埠;Socket負責發起連線操作。連線成功後,雙方通過輸入和輸出流進行同步阻塞式通訊。 

簡單的描述一下BIO的服務端通訊模型:採用BIO通訊模型的服務端,通常由一個獨立的Acceptor執行緒負責監聽客戶端的連線,它接收到客戶端連線請求之後為每個客戶端建立一個新的執行緒進行鏈路處理沒處理完成後,通過輸出流返回應答給客戶端,執行緒銷燬。即典型的一請求一應答通宵模型。

01

剛開始人們為了解決上面,高併發下伺服器建立執行緒過多而枯竭,有人就想出了使用執行緒池來控制建立執行緒的數量,不至於伺服器掛掉,於是就有了偽非同步的io程式設計

一(1)、偽非同步I/O程式設計

  為了改進這種一連線一執行緒的模型,我們可以使用執行緒池來管理這些執行緒(需要了解更多請參考前面提供的文章),實現1個或多個執行緒處理N個客戶端的模型(但是底層還是使用的同步阻塞I/O),通常被稱為“偽非同步I/O模型“。

02

 我們知道,如果使用CachedThreadPool執行緒池(不限制執行緒數量,如果不清楚請參考文首提供的文章),其實除了能自動幫我們管理執行緒(複用),看起來也就像是1:1的客戶端:執行緒數模型,而使用FixedThreadPool我們就有效的控制了執行緒的最大數量,保證了系統有限的資源的控制,實現了N:M的偽非同步I/O模型。

 但是,正因為限制了執行緒數量,如果發生大量併發請求,超過最大數量的執行緒就只能等待,直到執行緒池中的有空閒的執行緒可以被複用。

而對Socket的輸入流就行讀取時,會一直阻塞,直到發生:

有資料可讀

可用資料以及讀取完畢

發生空指標或I/O異常

所以在讀取資料較慢時(比如資料量大、網路傳輸慢等),大量併發的情況下,其他接入的訊息,只能一直等待,這就是最大的弊端。而後面即將介紹的NIO,就能解決這個難題。

二、NIO 程式設計(非阻塞I/O

JDK 1.4中的java.nio.*包中引入新的Java I/O庫,其目的是提高速度。實際上,“舊”的I/O包已經使用NIO重新實現過,即使我們不顯式的使用NIO程式設計,也能從中受益。速度的提高在檔案I/O和網路I/O中都可能會發生,但本文只討論後者。


(1)緩衝區buffer

    buffer是一個物件,包含了讀取和寫入的資料,在nio中,所有的資料都是通過緩衝區來處理的。在寫入資料時,也是寫入到緩衝區中。任何時候訪問NIO中的資料,都是通過緩衝區進行操作。

緩衝區實際是一個數組結構並提供了對資料結構化訪問以及維護讀寫位置等資訊。

    8種基本型別都有相應的緩衝區:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他們實現了相同的介面:Buffer。

(2)通道channel

    我們對資料的讀取和寫入都要通過channel,它就像水管一樣,是一個通道。通道不同於流的地方就是通道是雙向的,可以用於讀、寫和同時讀寫操作。

    底層的作業系統的通道一般都是全雙工的,所以全雙工的Channel比流能更好的對映底層作業系統的API。

    channel主要有2大類:

       selectablechannel 用於使用者網路的讀寫(後面程式碼會涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子類。

       Filechannel 用於檔案的操作

(3)多路複用器 Selector

    Selector是Java  NIO 程式設計的基礎。

提供選擇已經就緒的任務的能力:Selector會不斷輪詢註冊在其上的Channel,如果某個Channel上面發生讀或者寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作。

   一個Selector可以同時輪詢多個Channel,因為JDK使用了epoll()代替傳統的select實現,所以沒有最大連線控制代碼1024/2048的限制。所以,只需要一個執行緒負責Selector的輪詢,就可以接入成千上萬的客戶端。

(4)NIO服務端

  建立NIO服務端的主要步驟如下:

  1.   開啟ServerSocketChannel,監聽客戶端連線
  2.  繫結監聽埠,設定連線為非阻塞模式
  3.  建立Reactor執行緒,建立多路複用器並啟動執行緒
  4.  將ServerSocketChannel註冊到Reactor執行緒中的Selector上,監聽ACCEPT事件
  5.  Selector輪詢準備就緒的key
  6.  Selector監聽到新的客戶端接入,處理新的接入請求,完成TCP三次握手,簡歷物理鏈路
  7.  設定客戶端鏈路為非阻塞模式
  8.  將新接入的客戶端連線註冊到Reactor執行緒的Selector上,監聽讀操作,讀取客戶端傳送的網路訊息
  9.  非同步讀取客戶端訊息到緩衝區
  10.  對Buffer編解碼,處理半包訊息,將解碼成功的訊息封裝成Task
  11.  將應答訊息編碼為Buffer,呼叫SocketChannel的write將訊息非同步傳送給客戶端
    所以不能保證一次能吧需要傳送的資料傳送完,此時就會出現寫半包的問題。我們需要註冊寫操作,不斷輪詢Selector將沒有傳送完的訊息傳送完畢,然後通過Buffer的hasRemain()方法判斷訊息是否傳送完成。
(5)NIO客戶端 三、AIO程式設計

NIO 2.0引入了新的非同步通道的概念,並提供了非同步檔案通道和非同步套接字通道的實現。

非同步的套接字通道時真正的非同步非阻塞I/O,對應於UNIX網路程式設計中的事件驅動I/O(AIO)。他不需要過多的Selector對註冊的通道進行輪詢即可實現非同步讀寫,從而簡化了NIO的程式設計模型。

API比NIO的使用起來真的簡單多了,主要就是監聽、讀、寫等各種CompletionHandler。此處本應有一個WriteHandler的,確實,我們在ReadHandler中,以一個匿名內部類實現了它。

AIO是真正的非同步非阻塞的,所以,在面對超級大量的客戶端,更能得心應手

(5)各種I/O的對比

03

具體選擇什麼樣的模型或者NIO框架,完全基於業務的實際應用場景和效能需求,如果客戶端很少,伺服器負荷不重,就沒有必要選擇開發起來相對不那麼簡單的NIO做服務端;相反,就應考慮使用NIO或者相關的框架了。