Netty精粹之JAVA NIO开发需要知道的
學(xué)習(xí)Netty框架以及相關(guān)源碼也有一小段時間了,恰逢今天除夕,寫篇文章總結(jié)一下。Netty是個高效的JAVA NIO框架,總體框架基于異步非阻塞的設(shè)計,基于網(wǎng)絡(luò)IO事件驅(qū)動,主要貢獻在于可以讓用戶基于Netty提供的API快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)應(yīng)用。這篇文章主要是介紹Netty框架的基礎(chǔ)技術(shù)——JAVA NIO。這時候可能會有同學(xué)會有點小疑問,是異步IO(AIO)么?然而并不是,雖然JDK7也提供了異步IO(AIO)的接口,但是Netty曾經(jīng)嘗試過某個小版本,但是效果和NIO相比并沒有什么優(yōu)勢,因此后面的版本Netty也把對AIO的支持廢棄了,今天我們就來扒一下JAVA NIO。
?
四種IO模型簡述
我們先從四種IO模型開始扒起,常見的IO模型有四種(這四種模型在網(wǎng)絡(luò)上也有很多很多的資料,為較少篇幅本片將這部分內(nèi)容壓縮一下):
同步阻塞(Blocking IO):最簡單的一種IO模型,用戶線程在進行IO操作的時候通常是個系統(tǒng)調(diào)用,用戶線程會由用戶空間進入內(nèi)核空間,內(nèi)核空間數(shù)據(jù)包準(zhǔn)備好后會將數(shù)據(jù)拷貝到用戶空間,這個時候線程在用戶態(tài)繼續(xù)執(zhí)行。
同步非阻塞(Non-blocking IO):同步非阻塞IO即在同步阻塞的基礎(chǔ)之上將socket設(shè)置為NONBLOCK。這樣用戶線程在發(fā)起IO操作之后可以立即返回,但是用戶線程需要不斷輪詢來請求數(shù)據(jù)。
IO多路復(fù)用(IO Multiplexing):即Reactor設(shè)計模式,多路復(fù)用模型從流程上和同步阻塞的區(qū)別不大,主要區(qū)別在于操作系統(tǒng)為用戶提供了同時輪詢多個IO句柄來查看是否有IO事件的接口(如select),這從根本上允許用戶可以使用單個線程來管理多個IO句柄的問題。
異步IO(Asynchronous IO):即Proactor設(shè)計模式。在異步IO模型中,用戶不需要去輪詢IO事件,然后才進行數(shù)據(jù)的讀取,處理;在異步IO模型中,IO事件就緒的時候,內(nèi)核會開啟一個獨立的內(nèi)核線程去執(zhí)行執(zhí)行IO操作,實現(xiàn)真正的異步IO。這個時候用戶線程可以直接讀取內(nèi)核線程準(zhǔn)備好的數(shù)據(jù)。
多路復(fù)用IO模型和異步IO模型的區(qū)別主要是用戶線程得知IO事件的時候在多路復(fù)用IO模型中,用戶線程需要自己去處理IO,而在異步IO模型中數(shù)據(jù)已經(jīng)由內(nèi)核線程為用戶線程準(zhǔn)備好了。在實際應(yīng)用中,在高效的IO應(yīng)用中,最常見的是第三種IO模型,異步IO目前操作系統(tǒng)方面的支持并不是很好而且在性能數(shù)據(jù)上并不是很好看。
上面對四種IO模型進行了極其簡單的概括,如多讀者意猶未盡可以在網(wǎng)上查閱相關(guān)資料或者和作者聯(lián)系。
select、poll和epoll
JAVA對NIO的支持是從1.4版本開始的,是基于多路復(fù)用技術(shù),而在linux操作系統(tǒng)方面多路復(fù)用技術(shù)有三種常用的機制:select、poll和epoll,epoll的支持也只是linux2.6版本之后才提供,java在jdk5.0的update 9之后才對epoll進行支持。這三種機制本質(zhì)上都是同步IO,主要是由于他們都需要在讀寫事件就緒的時候需要自己進行讀寫,也就是這個這個讀寫過程是阻塞的。下面對著三種機制進行簡單總結(jié):
select函數(shù):改函數(shù)允許進程指示內(nèi)核等待多個事件中的任何一個發(fā)生的時候或者在一定時間之后被喚醒,select有個致命的缺點即在多路復(fù)用中文件描述符的數(shù)量有限制,如果需要突破限制需要重新編譯操作系統(tǒng)內(nèi)核。
poll函數(shù):poll機制與select機制類似,區(qū)別是poll沒有最大描述符限制。
epoll函數(shù):epoll在linux2.6內(nèi)核中被提出來,是之前的select和poll的增強版本。epoll也沒有文件描述符數(shù)量限制,而且是用一個文件描述符來管理多個描述符。在性能上相比上面兩種有了很大的優(yōu)化。
關(guān)于select、poll和epoll的詳細介紹可以參考這里。
JAVA NIO
JAVA的NIO是基于IO多路復(fù)用模型,在不同平臺上有不同的實現(xiàn)方式。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
在網(wǎng)絡(luò)IO方面,Channel的主要實現(xiàn)是ServerSocketChannel和SocketChannel。他們都代表一個面向流的可監(jiān)聽讀寫事件的socket。ServerSocketChannel是用于服務(wù)器端的socket,他提供了一個靜態(tài)工具方法open來為用戶提供獲取Channel的工具:
public?static?ServerSocketChannel?open()?throws?IOException?{ ????return?SelectorProvider.provider().openServerSocketChannel(); }其中涉及到的SelectorProvider用于創(chuàng)建具體的Channel,SelectorProvider的獲取有三種途徑,首先從系統(tǒng)屬性中獲取key為java.nio.channels.spi.SelectorProvider的值,如果沒有則基于SPI機制來獲取,如果再沒有則最后提供默認的,這個默認值跟操作系統(tǒng)平臺相關(guān),比如我的mac系統(tǒng),JDK提供的默認Provider是KQueueSelectorProvider。
ServerSocketChannel提供的接口
ServerSocketChannel的使用方式是面向服務(wù)器端的,一般的開發(fā)流程是:
獲取一個ServerSocketChannel。
設(shè)置網(wǎng)絡(luò)操作,這些參數(shù)主要是和TCP協(xié)議有關(guān)。
將ServerSocketChannel注冊到Selector(多路復(fù)用器)。
將ServerSocketChannel和某個具體的地址綁定。
用戶像多路復(fù)用器設(shè)置感興趣的IO事件。
用戶線程以阻塞或非阻塞方式輪詢Selector來查看是否有就緒的IO事件。
用戶針對不同的IO事件對Channel進行具體的IO操作。
SocketChannel主要是面向客戶端的開發(fā)的,也是以open方式獲取channel,客戶端的開發(fā)流程大致如下:
獲取一個SocketChannel。
設(shè)置Channel為非阻塞方式。
獲取Selector。
將channel注冊到Selector,并監(jiān)聽CONNECT事件。
調(diào)用channel的connect方法連接指定的服務(wù)器和端口。
如果連接成功則進行IO操作,如果沒成功則輪詢Selector處理CONNECT事件。
?
Selector
Selector是JAVA NIO中的多路復(fù)用器,配合SelectionKey使用,SelectionKey代表著一個Channel和Selector的關(guān)系的抽象,Channel向Selector注冊的時候產(chǎn)生,由Selector維護。Selector維護著三個SelectionKey的集合:
key set:這個集合包含所有向Selector注冊的Channel產(chǎn)生的SelectionKey,這個集合中的SelectionKey是不能直接被修改的,除非SelectionKey被channel,并且發(fā)生select的時候SelectionKey才被移出。
selected key set:這個集合是key set集合的子集,當(dāng)有SelectionKey關(guān)聯(lián)的Channel有Channel向Selector注冊的IO事件就緒的時候并且有select操作,對應(yīng)的SelectionKey會被放到selected key set中。因為這個集合中的SelectionKey可以通過直接調(diào)用Set的remove將SelectionKey移除。
cancelled-key:這個集合是也是key set的子集。當(dāng)有已經(jīng)向Selector注冊的Channel發(fā)生degistered的時候,SelectionKey將被放到這個集合,并且在下一次select的時候被從所有的集合中移出。
三種集合的流轉(zhuǎn)我畫個圖表示一下:
Selector的Selection Key集合流轉(zhuǎn)圖
在開發(fā)過程中,我們可以將多個Channel注冊到一個Selector實例中,用一個線程來處理所有的IO事件,我們也可以將多個Channel注冊到多個Selector實例中,結(jié)合高效的線程模型可以達到很好的效果。
?
ByteBuffer
JAVA NIO直接和Channel打交道的Buffer是ByteBuffer,ByteBuffer接口提供主要的內(nèi)存分配、IO讀寫等相關(guān)接口。值得注意的是JAVA NIO提供了兩種Buffer內(nèi)存分配機制,一種是堆內(nèi)存,另一種是直接內(nèi)存,主要區(qū)別:
堆內(nèi)存分配和回收比較快,但是網(wǎng)絡(luò)數(shù)據(jù)需要從內(nèi)核copy到堆中。
直接內(nèi)存分配和回收比較慢,但是免去了從內(nèi)核copy到堆中的一次copy。
這兩種內(nèi)存各有千秋,使用的時候要根據(jù)實際情況去選擇。
?
總結(jié):
這篇文章主要介紹一下JAVA NIO涉及到的一些基礎(chǔ)概念以及JAVA提供的NIO接口進行簡單介紹,JAVA NIO提供的接口使用起來,略復(fù)雜,實際項目中不建議直接使用JDK提供的API進行開發(fā)。Netty是一個基于JAVA NIO開發(fā)的可靠的JAVA NIO工具,Netty的精粹我認為除了IO模型之外還有下面的幾個部分:
高效的線程模型
內(nèi)存池技術(shù)
零copy技術(shù)
Netty是一個優(yōu)秀的開源NIO框架,我們可以使用它來快速構(gòu)建高性能的IO服務(wù)器,后面我會通過繼續(xù)深入學(xué)習(xí)和大家一起分享Netty的實現(xiàn)和原理。
?
from:https://my.oschina.net/andylucc/blog/614295
總結(jié)
以上是生活随笔為你收集整理的Netty精粹之JAVA NIO开发需要知道的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在VS 2010上搭建Windows P
- 下一篇: fastjson 1.2.57 版本发布