Netty精粹之JAVA NIO開發需要知道的
來自: http://my.oschina.net/andylucc/blog/614295
學習Netty框架以及相關源碼也有一小段時間了,恰逢今天除夕,寫篇文章總結一下。Netty是個高效的JAVA NIO框架,總體框架基于異步非阻塞的設計,基于網絡IO事件驅動,主要貢獻在于可以讓用戶基于Netty提供的API快速開發高性能、高可靠性的網絡應用。這篇文章主要是介紹Netty框架的基礎技術——JAVA NIO。這時候可能會有同學會有點小疑問,是異步IO(AIO)么?然而并不是,雖然JDK7也提供了異步IO(AIO)的接口,但是Netty曾經嘗試過某個小版本,但是效果和NIO相比并沒有什么優勢,因此后面的版本Netty也把對AIO的支持廢棄了,今天我們就來扒一下JAVA NIO。
四種IO模型簡述
我們先從四種IO模型開始扒起,常見的IO模型有四種(這四種模型在網絡上也有很多很多的資料,為較少篇幅本片將這部分內容壓縮一下):
同步阻塞(Blocking IO):最簡單的一種IO模型,用戶線程在進行IO操作的時候通常是個系統調用,用戶線程會由用戶空間進入內核空間,內核空間數據包準備好后會將數據拷貝到用戶空間,這個時候線程在用戶態繼續執行。
同步非阻塞(Non-blocking IO):同步非阻塞IO即在同步阻塞的基礎之上將socket設置為NONBLOCK。這樣用戶線程在發起IO操作之后可以立即返回,但是用戶線程需要不斷輪詢來請求數據。
IO多路復用(IO Multiplexing):即Reactor設計模式,多路復用模型從流程上和同步阻塞的區別不大,主要區別在于操作系統為用戶提供了同時輪詢多個IO句柄來查看是否有IO事件的接口(如select),這從根本上允許用戶可以使用單個線程來管理多個IO句柄的問題。
異步IO(Asynchronous IO):即Proactor設計模式。在異步IO模型中,用戶不需要去輪詢IO事件,然后才進行數據的讀取,處理;在異步IO模型中,IO事件就緒的時候,內核會開啟一個獨立的內核線程去執行執行IO操作,實現真正的異步IO。這個時候用戶線程可以直接讀取內核線程準備好的數據。
多路復用IO模型和異步IO模型的區別主要是用戶線程得知IO事件的時候在多路復用IO模型中,用戶線程需要自己去處理IO,而在異步IO模型中數據已經由內核線程為用戶線程準備好了。在實際應用中,在高效的IO應用中,最常見的是第三種IO模型,異步IO目前操作系統方面的支持并不是很好而且在性能數據上并不是很好看。
上面對四種IO模型進行了極其簡單的概括,如多讀者意猶未盡可以在網上查閱相關資料或者和作者聯系。
select、poll和epoll
JAVA對NIO的支持是從1.4版本開始的,是基于多路復用技術,而在linux操作系統方面多路復用技術有三種常用的機制:select、poll和epoll,epoll的支持也只是linux2.6版本之后才提供,java在jdk5.0的update 9之后才對epoll進行支持。這三種機制本質上都是同步IO,主要是由于他們都需要在讀寫事件就緒的時候需要自己進行讀寫,也就是這個這個讀寫過程是阻塞的。下面對著三種機制進行簡單總結:
select函數:改函數允許進程指示內核等待多個事件中的任何一個發生的時候或者在一定時間之后被喚醒,select有個致命的缺點即在多路復用中文件描述符的數量有限制,如果需要突破限制需要重新編譯操作系統內核。
poll函數:poll機制與select機制類似,區別是select沒有最大描述符限制。
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的使用方式是面向服務器端的,一般的開發流程是:
獲取一個ServerSocketChannel。
設置網絡操作,這些參數主要是和TCP協議有關。
將ServerSocketChannel注冊到Selector(多路復用器)。
將ServerSocketChannel和某個具體的地址綁定。
用戶像多路復用器設置感興趣的IO事件。
用戶線程以阻塞或非阻塞方式輪詢Selector來查看是否有就緒的IO事件。
用戶針對不同的IO事件對Channel進行具體的IO操作。
SocketChannel主要是面向客戶端的開發的,也是以open方式獲取channel,客戶端的開發流程大致如下:
獲取一個SocketChannel。
設置Channel為非阻塞方式。
獲取Selector。
將channel注冊到Selector,并監聽CONNECT事件。
調用channel的connect方法連接指定的服務器和端口。
如果連接成功則進行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內存分配機制,一種是堆內存,另一種是直接內存,主要區別:
堆內存分配和回收比較快,但是網絡數據需要從內核copy到堆中。
直接內存分配和回收比較慢,但是免去了從內核copy到堆中的一次copy。
這兩種內存各有千秋,使用的時候要根據實際情況去選擇。
總結:
這篇文章主要介紹一下JAVA NIO涉及到的一些基礎概念以及JAVA提供的NIO接口進行簡單介紹,JAVA NIO提供的接口使用起來,略復雜,實際項目中不建議直接使用JDK提供的API進行開發。Netty是一個基于JAVA NIO開發的可靠的JAVA NIO工具,Netty的精粹我認為除了IO模型之外還有下面的幾個部分:
高效的線程模型
內存池技術
零copy技術
Netty是一個優秀的開源NIO框架,我們可以使用它來快速構建高性能的IO服務器,后面我會通過繼續深入學習和大家一起分享Netty的實現和原理。
作者:陸晨
時間:除夕