說道nio,不得不從io模型說起。
阻塞I/O模型
最常見的I/O模型是阻塞I/O模型,缺省情形下,所有文件操作都是阻塞的。我們以套接口爲例來講解此模型。在進程空間中調用recvfrom,其系統調用直到數據報到達且被拷貝到應用進程的緩衝區中或者發生錯誤才返回,期間一直在等待。我們就說進程在從調用recvfrom開始到它返回的整段時間內是被阻塞的。
非阻塞I/O模型
進程把一個套接口設置成非阻塞是在通知內核:當所請求的I/O操作不能滿足要求時候,不把本進程投入睡眠,而是返回一個錯誤。也就是說當數據沒有到達時並不等待,而是以一個錯誤返回。
I/O複用模型
系統提供select/poll,進程通過將一個或多個fd傳遞給select或poll系統調用,阻塞在select;這樣select/poll可以幫我們偵測許多fd是否就緒。但是select/poll是順序掃描fd是否就緒,而且支持的fd數量有限。系統還提供了一個epoll系統調用,epoll是基於事件驅動方式,而不是順序掃描,當有fd就緒時,立即回調函數rollback。
信號驅動異步I/O模型
首先開啓套接口信號驅動I/O功能, 並通過系統調用sigaction安裝一個信號處理函數(此係統調用立即返回,進程繼續工作,它是非阻塞的)。當數據報準備好被讀時,就爲該進程生成一個SIGIO信號。隨即可以在信號處理程序中調用recvfrom來讀數據報,井通知主循環數據已準備好被處理中。也可以通知主循環,讓它來讀數據報。
異步I/O模型
告知內核啓動某個操作,並讓內核在整個操作完成後(包括將數據從內核拷貝到用戶自己的緩衝區)通知我們。這種模型與信號驅動模型的主要區別是:信號驅動I/O:由內核通知我們何時可以啓動一個I/O操作;異步I/O模型:由內核通知我們I/O操作何時完成。
nio:服務器端保存一個Socket連接列表,然後對這個列表進行輪詢,如果發現某個Socket端口上有數據可讀時(讀就緒),則調用該socket連接的相應讀操作;如果發現某個 Socket端口上有數據可寫時(寫就緒),則調用該socket連接的相應寫操作;如果某個端口的Socket連接已經中斷,則調用相應的析構方法關閉該端口。這樣能充分利用服務器資源,效率得到了很大提高。Java中使用Selector、Channel、Buffer來實現上述效果,如下圖所示:
精簡版:
詳細版:
沒有比較就沒有傷害,再回過來看看bio。
bio:每建立一個Socket連接時,同時創建一個新線程對該Socket進行單獨通信(採用阻塞的方式通信)。這種方式具有很高的響應速度,並且控制起來也很簡單,在連接數較少的時候非常有效,但是如果對每一個連接都產生一個線程的無疑是對系統資源的一種浪費,如果連接數較多將會出現資源不足的情況。
他有以下兩個缺點:
(a)使用一個獨立的線程維護一個socket連接,隨着連接數量的增多,對虛擬機造成一定壓力;
(b)使用流來讀取數據,流是阻塞的,當沒有可讀/可寫數據時,線程等待,會造成資源的浪費
相比而言,nio利用selector輪詢+線程的方式完美解決了bio的兩大缺點!
Selector
Selector是Java NIO中用於管理一個或多個Channel的組件,控制決定對哪些Channel進行讀寫;通過使用Selector讓一個單線程可以管理多個Channel甚至多個網絡連接。
使用Selector最大的優勢就是可以在較少的線程中控制更多的Channel。事實上我們可以使用一個線程控制需要使用的所有Channel。操作系統線程的運行和切換需要一定的開銷,使用的線程越小,系統開銷也就越少;因此使用Selector可以節省很多系統開銷。
Channel
JavaNIO Channel和流有一些相似,但是又有些不同:
你可以同時讀和寫Channel,流Stream只支持單向的讀和寫。
Channel可以異步的讀和寫,流Stream是同步的。
Channel總是讀取到buffer或者從buffer中寫。
下面分別介紹一下Channel最重要的一些實現類:
FileChannel : 可以讀寫文件中的數據。
DatagramChannel:可以通過UDP協議讀寫數據。
SocketChannel:可以通過TCP協議讀寫數據。
ServerSocketChannel:允許我們像一個web服務器那樣監聽TCP鏈接請求,爲每一個鏈接請求創建一個SocketChannel。
Buffer
在Java NIO中各類Buffer主要用於和NIO Channel進行交互,數據從Channel中讀取到Buffer中,從Buffer寫入到Channel中。我們可以將Buffer看做內存中的一塊區域,我們可以在這塊區域上寫數據,然後在從中讀取。這塊內存區域被包裝成NIO Buffer對象,提供了一系列的方法使我們操作這塊內存變得更簡單一些。
小結:
nio因爲擁有了非阻塞的設計和利用selector的輪詢方式,使得系統變得更加容易擴展,甚至可以把selector分佈式化,在分佈式網絡編程中nio纔是真正的如魚得水,實現正真的高可用設計。Mina、Netty、Grizzly都是典型的例子。
本期介紹就到這裏啦,喜歡小碼農的請掃碼關注公衆號。加我微信號拉你進java菜雞交流羣,java大廠內推羣,java進階高手羣!