1. 程式人生 > >java NIO 學習

java NIO 學習

之間 理解 poll 利用 .com 根據 handler react 階段

一、了解Unix網絡編程5種I/O模型

1.1、阻塞式I/O模型

技術分享

阻塞I/O(blocking I/O)模型,進程調用recvfrom,其系統調用直到數據報到達且被拷貝到應用進程的緩沖區中或者發生錯誤才返回。進程從調用recvfrom開始到它返回的整段時間內是被阻塞的。

1.2、非阻塞式I/O模型

技術分享

當一個應用進程像這樣對一個非阻塞描述字循環調用recvfrom時,我們稱之為輪詢(polling)。應用進程持續輪詢內核,以查看某個操作是否就緒。

1.3、I/O多路復用(事件驅動)模型

技術分享

1.4、信號驅動式I/O(SIGIO)

技術分享

1.5、異步I/O模型

技術分享

1.6、I/O模型的比較:

技術分享

根據上述5種IO模型,前4種模型-阻塞IO、非阻塞IO、IO復用、信號驅動IO都是同步I/O模型,因為其中真正的I/O操作(recvfrom)將阻塞進程,在內核數據copy到用戶空間時都是阻塞的。

1.7、同步IO、異步IO、阻塞IO、非阻塞IO

一個IO操作可以分為兩個步驟:發起IO請求和實際的IO操作
例如:
1、操作系統的一次寫操作分為兩步:將數據從用戶空間拷貝到系統空間;從系統空間往網卡寫。
2、一次讀操作分為兩步:將數據從網卡拷貝到系統空間;將數據從系統空間拷貝到用戶空間。

阻塞IO和非阻塞IO的區別在於第一步,發起IO請求是否會被阻塞,如果阻塞直到完成那麽就是傳統的阻塞IO,如果不阻塞,那麽就是非阻塞IO。

同步IO和異步IO的區別就在於第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求進程,那麽就是同步IO,因此阻塞IO、非阻塞IO、IO復用、信號驅動IO都是同步IO,如果不阻塞,而是操作系統做完IO兩個階段的操作再將結果返回,那麽就是異步IO。

1.8、IO多路復用

IO多路復用,就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。

從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以後最大的優勢是用戶可以在一個線程內同時處理多個socket的IO請求。用戶可以註冊多個socket,然後不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。

IO多路復用方式允許單線程內處理多個IO請求,但是每個IO請求的過程還是阻塞的(在select函數上阻塞),平均時間甚至比同步阻塞IO模型還要長。如果用戶線程只註冊自己感興趣的socket或者IO請求,然後去做自己的事情,等到數據到來時再進行處理,則可以提高CPU的利用率。
由於select函數是阻塞的,因此多路IO復用模型也被稱為異步阻塞IO模型。註意,這裏的所說的阻塞是指select函數執行時線程被阻塞,而不是指socket。一般在使用IO多路復用模型時,socket都是設置為NONBLOCK的,不過這並不會產生影響,因為用戶發起IO請求時,數據已經到達了,用戶線程一定不會被阻塞。
IO多路復用是最常使用的IO模型,但是其異步程度還不夠“徹底”,因為它使用了會阻塞線程的select系統調用。因此IO多路復用只能稱為異步阻塞IO,而非真正的異步IO。

技術分享
展示了非阻塞IO如何讓你使用一個selector區處理多個連接.

1.9、select、poll、epoll

Linux支持IO多路復用的系統調用有select、poll、epoll,這些調用都是內核級別的。但select、poll、epoll本質上都是同步I/O,先是block住等待就緒的socket,再是block住將數據從內核拷貝到用戶內存。

select、poll、epoll之間的區別,如下表:
技術分享

1.10、兩種I/O多路復用模式:Reactor和Proactor

在這兩種模式下的事件多路分離器反饋給程序的信息是不一樣的:
1.Reactor模式下說明你可以進行讀寫(收發)操作了。
2.Proactor模式下說明已經完成讀寫(收發)操作了,具體內容在給定緩沖區中,可以對這些內容進行其他操作了。
Reactor關註的是I/O操作的就緒事件,而Proactor關註的是I/O操作的完成事件

一般地,I/O多路復用機制都依賴於一個事件多路分離器(Event Demultiplexer)。分離器對象可將來自事件源的I/O事件分離出來,並分發到對應的read/write事件處理器(Event Handler)。

Reactor模式采用同步IO,而Proactor采用異步IO。

在Reactor中,事件分離器負責等待文件描述符或socket為讀寫操作準備就緒,然後將就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

而在Proactor模式中,處理器或者兼任處理器的事件分離器,只負責發起異步讀寫操作。IO操作本身由操作系統來完成。傳遞給操作系統的參數需要包括用戶定義的數據緩沖區地址和數據大小,操作系統才能從中得到寫出操作所需數據,或寫入從socket讀到的數據。事件分離器捕獲IO操作完成事件,然後將事件傳遞給對應處理器。比如,在windows上,處理器發起一個異步IO操作,再由事件分離器等待IOCompletion事件。典型的異步模式實現,都建立在操作系統支持異步API的基礎之上,我們將這種實現稱為“系統級”異步或“真”異步,因為應用程序完全依賴操作系統執行真正的IO工作。

Reactor和Proactor模式的主要區別就是真正的讀取和寫入操作是有誰來完成的,Reactor中需要應用程序自己讀取或者寫入數據,而Proactor模式中,應用程序不需要進行實際的讀寫過程,它只需要從緩存區讀取或者寫入即可,操作系統會讀取緩存區或者寫入緩存區到真正的IO設備.

二、Java NIO

NIO,有人稱之為New I/O,因為它相對於之前的I/O類庫是新增的,所以被稱為New I/O。但是,由於之前老的 I/O 類庫是阻塞 I/O,New I/O類庫的目標就是要讓Java支持非阻塞 I/O,所以,更多的人喜歡稱之為非阻塞 I/ O(Non-block I/O)。

2.1、對NIO的非阻塞的理解

註意,select是阻塞的,無論是通過操作系統的通知(epoll)還是不停的輪詢(select,poll),這個函數是阻塞的。所以你可以放心大膽地在一個while(true)裏面調用這個函數而不用擔心CPU空轉。

NIO采用Reactor模式,一個Reactor線程聚合一個多路復用器Selector,它可以同時註冊、監聽和輪詢成百上千個Channel,一個IO線程可以同時並發處理N個客戶端連接,線程模型優化為1:N(N < 進程可用的最大句柄數)或者M : N (M通常為CPU核數 + 1, N < 進程可用的最大句柄數)。

JAVA NIO 不是同步非阻塞I/O嗎,為什麽說JAVA NIO提供了基於Selector的異步網絡I/O?
java nio的io模型是同步非阻塞,這裏的同步異步指的是真正io操作(數據內核態用戶態的拷貝)是否需要進程參與。
而說java nio提供了異步處理,這個異步應該是指編程模型上的異步。基於reactor模式的事件驅動,事件處理器的註冊和處理器的執行是異步的。

AIO(Async I/O)裏面會更進一步:不但等待就緒是非阻塞的,就連數據從網卡到內存的過程也是異步的。
換句話說,BIO裏用戶最關心“我要讀”,NIO裏用戶最關心"我可以讀了",在AIO模型裏用戶更需要關註的是“讀完了”。
NIO一個重要的特點是:socket主要的讀、寫、註冊和接收函數,在等待就緒階段都是非阻塞的,真正的I/O操作是同步的(消耗CPU但性能非常高)。

2.2、如何結合事件模型使用NIO非阻塞特性

BIO模型,之所以需要多線程,是因為在進行I/O操作的時候,一是沒有辦法知道到底能不能寫、能不能讀,只能"傻等",即使通過各種估算,算出來操作系統沒有能力進行讀寫,也沒法在socket.read()和socket.write()函數中返回,這兩個函數無法進行有效的中斷。所以除了多開線程另起爐竈,沒有好的辦法利用CPU。

NIO的讀寫函數可以立刻返回,這就給了我們不開線程利用CPU的最好機會:如果一個連接不能讀寫(socket.read()返回0或者socket.write()返回0),我們可以把這件事記下來,記錄的方式通常是在Selector上註冊標記位,然後切換到其它就緒的連接(channel)繼續進行讀寫。

我們大概可以總結出NIO是怎麽解決掉線程的瓶頸並處理海量連接的:

NIO由原來的阻塞讀寫(占用線程)變成了單線程輪詢事件,找到可以進行讀寫的網絡描述符進行讀寫。除了事件的輪詢是阻塞的(沒有可幹的事情必須要阻塞),剩余的I/O操作都是純CPU操作,沒有必要開啟多線程。
並且由於線程的節約,連接數大的時候因為線程切換帶來的問題也隨之解決,進而為處理海量連接提供了可能。

2.3、理解異步非阻塞I/O

很多人喜歡將JDK1.4提供的NIO框架稱為異步非阻塞I/O,但是,如果嚴格按照UNIX網絡編程模型和JDK的實現進行區分,實際上它只能被稱為非阻塞I/O,不能叫異步非阻塞I/O。在早期的JDK1.4和1.5 update10版本之前,JDK的Selector基於select/poll模型實現,它是基於I/O復用技術的非阻塞I/O,不是異步I/O。在JDK1.5 update10和Linux core2.6以上版本,Sun優化了Selctor的實現,它在底層使用epoll替換了select/poll,上層的API並沒有變化,可以認為是JDK NIO的一次性能優化,但是它仍舊沒有改變I/O的模型。
由JDK1.7提供的NIO2.0,新增了異步的套接字通道,它是真正的異步I/O,在異步I/O操作的時候可以傳遞信號變量,當操作完成之後會回調相關的方法,異步I/O也被稱為AIO。
NIO類庫支持非阻塞讀和寫操作,相比於之前的同步阻塞讀和寫,它是異步的,因此很多人習慣於稱NIO為異步非阻塞I/O,包括很多介紹NIO編程的書籍也沿用了這個說法。為了符合大家的習慣,我們也將NIO稱為異步非阻塞I/O或者非阻塞I/O。

技術分享

三、Java NIO的核心組成

3.1、通道(Channel) 和 緩沖區(Buffer)

基本上,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 數據可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。這裏有個圖示:
技術分享

3.2、多路復用器(Selector)

Selector允許單線程處理多個Channel。如果你的應用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。例如,在一個聊天服務器中。

這是在一個單線程中使用一個Selector處理3個Channel的圖示:
技術分享

要使用Selector,得向Selector註冊Channel,然後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等。

四、總結

最後總結一下到底NIO給我們帶來了些什麽:
事件驅動模型
避免多線程
單線程處理多任務
非阻塞I/O,I/O讀寫不再阻塞,而是返回0
基於block的傳輸,通常比基於流的傳輸更高效
更高級的IO函數,zero-copy
IO多路復用大大提高了Java網絡應用的可伸縮性和實用性

java NIO 學習