Netty精粹之JAVA NIO開發須要知道的

學習Netty框架以及相關源碼也有一小段時間了,恰逢今天除夕,寫篇文章總結一下。Netty是個高效的JAVA NIO框架,整體框架基於異步非阻塞的設計,基於網絡IO事件驅動,主要貢獻在於可讓用戶基於Netty提供的API快速開發高性能、高可靠性的網絡應用。這篇文章主要是介紹Netty框架的基礎技術——JAVA NIO。這時候可能會有同窗會有點小疑問,是異步IO(AIO)麼?然而並非,雖然JDK7也提供了異步IO(AIO)的接口,可是Netty曾經嘗試過某個小版本,可是效果和NIO相比並無什麼優點,所以後面的版本Netty也把對AIO的支持廢棄了,今天咱們就來扒一下JAVA NIO。html


四種IO模型簡述java

咱們先從四種IO模型開始扒起,常見的IO模型有四種(這四種模型在網絡上也有不少不少的資料,爲較少篇幅本片將這部份內容壓縮一下):linux

  1. 同步阻塞(Blocking IO):最簡單的一種IO模型,用戶線程在進行IO操做的時候一般是個系統調用,用戶線程會由用戶空間進入內核空間,內核空間數據包準備好後會將數據拷貝到用戶空間,這個時候線程在用戶態繼續執行。
    設計模式

  2. 同步非阻塞(Non-blocking IO):同步非阻塞IO即在同步阻塞的基礎之上將socket設置爲NONBLOCK。這樣用戶線程在發起IO操做以後能夠當即返回,可是用戶線程須要不斷輪詢來請求數據。
    服務器

  3. IO多路複用(IO Multiplexing):即Reactor設計模式,多路複用模型從流程上和同步阻塞的區別不大,主要區別在於操做系統爲用戶提供了同時輪詢多個IO句柄來查看是否有IO事件的接口(如select),這從根本上容許用戶可使用單個線程來管理多個IO句柄的問題。
    網絡

  4. 異步IO(Asynchronous IO):即Proactor設計模式。在異步IO模型中,用戶不須要去輪詢IO事件,而後才進行數據的讀取,處理;在異步IO模型中,IO事件就緒的時候,內核會開啓一個獨立的內核線程去執行執行IO操做,實現真正的異步IO。這個時候用戶線程能夠直接讀取內核線程準備好的數據。
    框架

多路複用IO模型和異步IO模型的區別主要是用戶線程得知IO事件的時候在多路複用IO模型中,用戶線程須要本身去處理IO,而在異步IO模型中數據已經由內核線程爲用戶線程準備好了。在實際應用中,在高效的IO應用中,最多見的是第三種IO模型,異步IO目前操做系統方面的支持並非很好並且在性能數據上並非很好看。異步

上面對四種IO模型進行了極其簡單的歸納,如多讀者意猶未盡能夠在網上查閱相關資料或者和做者聯繫。socket

select、poll和epollide

JAVA對NIO的支持是從1.4版本開始的,是基於多路複用技術,而在linux操做系統方面多路複用技術有三種經常使用的機制:select、poll和epoll,epoll的支持也只是linux2.6版本以後才提供,java在jdk5.0的update 9以後纔對epoll進行支持。這三種機制本質上都是同步IO,主要是因爲他們都須要在讀寫事件就緒的時候須要本身進行讀寫,也就是這個這個讀寫過程是阻塞的。下面對着三種機制進行簡單總結:

  1. select函數:改函數容許進程指示內核等待多個事件中的任何一個發生的時候或者在必定時間以後被喚醒,select有個致命的缺點即在多路複用中文件描述符的數量有限制,若是須要突破限制須要從新編譯操做系統內核。

  2. poll函數:poll機制與select機制相似,區別是poll沒有最大描述符限制。

  3. epoll函數:epoll在linux2.6內核中被提出來,是以前的select和poll的加強版本。epoll也沒有文件描述符數量限制,並且是用一個文件描述符來管理多個描述符。在性能上相比上面兩種有了很大的優化。

關於select、poll和epoll的詳細介紹能夠參考這裏

JAVA NIO

JAVA的NIO是基於IO多路複用模型,在不一樣平臺上有不一樣的實現方式。Linux下面用的是poll和epoll,在BSD上用kqueue,在Windows上是重疊I/O。

在JAVA NIO中有三個核心的組件:Channels、Buffers和Selectors。

JAVA NIO核心組件

在JAVA NIO中,基本上全部的IO都是從Channel開始的,讀取操做即從Channel讀到Buffer,寫操做即從Buffer寫入Channel。

NIO讀寫示意圖


Channel

在網絡IO方面,Channel的主要實現是ServerSocketChannel和SocketChannel。他們都表明一個面向流的可監聽讀寫事件的socket。ServerSocketChannel是用於服務器端的socket,他提供了一個靜態工具方法open來爲用戶提供獲取Channel的工具:

public static ServerSocketChannel open() throws IOException {
    return SelectorProvider.provider().openServerSocketChannel();
}

其中涉及到的SelectorProvider用於建立具體的Channel,SelectorProvider的獲取有三種途徑,首先從系統屬性中獲取key爲java.nio.channels.spi.SelectorProvider的值,若是沒有則基於SPI機制來獲取,若是再沒有則最後提供默認的,這個默認值跟操做系統平臺相關,好比個人mac系統,JDK提供的默認Provider是KQueueSelectorProvider。

ServerSocketChannel提供的接口

ServerSocketChannel的使用方式是面向服務器端的,通常的開發流程是:

  1. 獲取一個ServerSocketChannel。

  2. 設置網絡操做,這些參數主要是和TCP協議有關。

  3. 將ServerSocketChannel註冊到Selector(多路複用器)。

  4. 將ServerSocketChannel和某個具體的地址綁定。

  5. 用戶像多路複用器設置感興趣的IO事件。

  6. 用戶線程以阻塞或非阻塞方式輪詢Selector來查看是否有就緒的IO事件。

  7. 用戶針對不一樣的IO事件對Channel進行具體的IO操做。

SocketChannel主要是面向客戶端的開發的,也是以open方式獲取channel,客戶端的開發流程大體以下:

  1. 獲取一個SocketChannel。

  2. 設置Channel爲非阻塞方式。

  3. 獲取Selector。

  4. 將channel註冊到Selector,並監聽CONNECT事件。

  5. 調用channel的connect方法鏈接指定的服務器和端口。

  6. 若是鏈接成功則進行IO操做,若是沒成功則輪詢Selector處理CONNECT事件。


Selector

Selector是JAVA NIO中的多路複用器,配合SelectionKey使用,SelectionKey表明着一個Channel和Selector的關係的抽象,Channel向Selector註冊的時候產生,由Selector維護。Selector維護着三個SelectionKey的集合:

key set:這個集合包含全部向Selector註冊的Channel產生的SelectionKey,這個集合中的SelectionKey是不能直接被修改的,除非SelectionKey被channel,而且發生select的時候SelectionKey才被移出。

selected key set:這個集合是key set集合的子集,當有SelectionKey關聯的Channel有Channel向Selector註冊的IO事件就緒的時候而且有select操做,對應的SelectionKey會被放到selected key set中。由於這個集合中的SelectionKey能夠經過直接調用Set的remove將SelectionKey移除。

cancelled-key:這個集合是也是key set的子集。當有已經向Selector註冊的Channel發生degistered的時候,SelectionKey將被放到這個集合,而且在下一次select的時候被從全部的集合中移出。

三種集合的流轉我畫個圖表示一下:

Selector的Selection Key集合流轉圖

在開發過程當中,咱們能夠將多個Channel註冊到一個Selector實例中,用一個線程來處理全部的IO事件,咱們也能夠將多個Channel註冊到多個Selector實例中,結合高效的線程模型能夠達到很好的效果。


ByteBuffer

JAVA NIO直接和Channel打交道的Buffer是ByteBuffer,ByteBuffer接口提供主要的內存分配、IO讀寫等相關接口。值得注意的是JAVA NIO提供了兩種Buffer內存分配機制,一種是堆內存,另外一種是直接內存,主要區別:

  1. 堆內存分配和回收比較快,可是網絡數據須要從內核copy到堆中。

  2. 直接內存分配和回收比較慢,可是免去了從內核copy到堆中的一次copy。

這兩種內存各有千秋,使用的時候要根據實際狀況去選擇。


總結:

這篇文章主要介紹一下JAVA NIO涉及到的一些基礎概念以及JAVA提供的NIO接口進行簡單介紹,JAVA NIO提供的接口使用起來,略複雜,實際項目中不建議直接使用JDK提供的API進行開發。Netty是一個基於JAVA NIO開發的可靠的JAVA NIO工具,Netty的精粹我認爲除了IO模型以外還有下面的幾個部分:

  1. 高效的線程模型

  2. 內存池技術

  3. 零copy技術

Netty是一個優秀的開源NIO框架,咱們可使用它來快速構建高性能的IO服務器,後面我會經過繼續深刻學習和你們一塊兒分享Netty的實現和原理。



做者:陸晨

時間:除夕

相關文章
相關標籤/搜索