Netty源碼學習(1)--java nio

? ? Java??

? ? NIO 由以下幾個核心部分組成:

? ? Channels、Buffers、Selectors

? ? 雖然Java NIO 中除此之外還有很多類和組件,但在我看來,Channel,Buffer 和Selector 構成了核心的API。其它組件,如Pipe和FileLock,只不過是與三個核心組件共同使用的工具類。


Channel 和Buffer

? ? ? ? 基本上,所有的 IO 在NIO中都從一個Channel 開始。Channel 有點象流。數據可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。

? ? ? ? Channel和Buffer有好幾種類型。下面是JAVA NIO中的一些主要Channel的實現:

? ? ? ? FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel

? ? ? ? 正如你所看到的,這些通道涵蓋了UDP 和 TCP 網絡IO,以及文件IO。與這些類一起的有一些有趣的接口,但為簡單起見,我盡量在概述中不提到它們。本教程其它章節與它們相關的地方我會進行解釋。以下是Java NIO里關鍵的Buffer實現:

? ? ? ?ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer

? ? ? 這些Buffer覆蓋了你能通過IO發送的基本數據類型:byte, short, int, long, float,double 和 char。Java NIO 還有個 MappedByteBuffer,用于表示內存映射文件。

? ? ? ?Selector

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


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

Buffer的使用


? ? ? ?總結出使用Buffer一般遵循下面幾個步驟:

? ? ? ? 1. 分配空間(ByteBuffer buf = ByteBuffer.allocate(1024); 還有一種allocateDirector后面再陳述)

? ? ? ? 2. 寫入數據到Buffer(int bytesRead = fileChannel.read(buf);)

? ? ? ? 3. 調用filp()方法( buf.flip();)

? ? ? ? 4. 從Buffer中讀取數據(System.out.print((char)buf.get());)

? ? ? ? 5. 調用clear()方法或者compact()方法

? ? ? ?Buffer顧名思義:緩沖區,實際上是一個容器,一個連續數組。Channel提供從文件、網絡讀取數據的渠道,但是讀寫的數據都必須經過Buffer。如圖:


向Buffer中寫數據:

? ? ? ?從Channel寫到Buffer (fileChannel.read(buf))

? ? ? ?通過Buffer的put()方法 (buf.put(…))

從Buffer中讀取數據:

? ? ? ?從Buffer讀取到Channel (channel.write(buf))

? ? ? ?使用get()方法從Buffer中讀取數據 (buf.get())

? ? ? ? 可以把Buffer簡單地理解為一組基本數據類型的元素列表,它通過幾個變量來保存這個數據的當前位置狀態:capacity, position, limit, mark: 具體說明如圖表所示:

? ? ? ? capacity

? ? ? ? 作為一個內存塊,Buffer有一個固定的大小值,也叫“capacity”.你只能往里寫capacity個byte、long,char等類型。一旦Buffer滿了,需要將其清空(通過讀數據或者清除數據)才能繼續寫數據往里寫數據。

? ? ? ? position

? ? ? ? 當你寫數據到Buffer中時,position表示當前的位置。初始的position值為0.當一個byte、long等數據寫到Buffer后,position會向前移動到下一個可插入數據的Buffer單元。position最大可為capacity – 1.當讀取數據時,也是從某個特定位置讀。當將Buffer從寫模式切換到讀模式,position會被重置為0. 當從Buffer的position處讀取數據時,position向前移動到下一個可讀的位置。

? ? ? ? ?limit

? ? ? ? 在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數據。寫模式下,limit等于Buffer的capacity。當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)

? ? ? ?flip()方法

? ? ? ?flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,并將limit設置成之前position的值。換句話說,position現在用于標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 現在能讀取多少個byte、char等。

? ? ? ? Scatter/Gather

? ? ? ?Java NIO開始支持scatter/gather,scatter/gather用于描述從Channel(譯者注:Channel在中文經常翻譯為通道)中讀取或者寫入到Channel的操作。

分散(scatter)從Channel中讀取是指在讀操作時將讀取的數據寫入多個buffer中。因此,Channel將從Channel中讀取的數據“分散(scatter)”到多個Buffer中。

聚集(gather)寫入Channel是指在寫操作時將多個buffer的數據寫入同一個Channel,因此,Channel 將多個Buffer中的數據“聚集(gather)”后發送到Channel。

? ? ? ?scatter/ gather經常用于需要將傳輸的數據分開處理的場合,例如傳輸一個由消息頭和消息體組成的消息,你可能會將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體。

代碼示例如下:


注意buffer首先被插入到數組,然后再將數組作為channel.read() 的輸入參數。read()方法按照buffer在數組中的順序將從channel中讀取的數據寫入到buffer,當一個buffer被寫滿后,channel緊接著向另一個buffer中寫。

Scattering

Reads在移動下一個buffer前,必須填滿當前的buffer,這也意味著它不適用于動態消息(譯者注:消息大小不固定)。換句話說,如果存在消息頭和消息體,消息頭必須完成填充(例如 128byte),Scattering Reads才能正常工作。

GatheringWrites

Gathering

Writes是指數據從多個buffer寫入到同一個channel。

代碼示例如下:

buffers數組是write()方法的入參,write()方法會按照buffer在數組中的順序,將數據寫入到channel,注意只有position和limit之間的數據才會被寫入。因此,如果一個buffer的容量為128byte,但是僅僅包含58byte的數據,那么這58byte的數據將被寫入到channel中。因此與Scattering Reads相反,Gathering Writes能較好的處理動態消息。

? ? ? ?舉例:我們通過ByteBuffer.allocate(11)方法創建了一個11個byte的數組的緩沖區,初始狀態如圖,position的位置為0,capacity和limit默認都是數組長度。


當我們寫入5個字節時,變化如下圖:


? ? ? ? 這時我們需要將緩沖區中的5個字節數據寫入Channel的通信信道,所以我們調用ByteBuffer.flip()方法,變化如下圖所示(position設回0,并將limit設成之前的position的值):


這時底層操作系統就可以從緩沖區中正確讀取這個5個字節數據并發送出去了。在下一次寫數據之前我們再調用clear()方法,緩沖區的索引位置又回到了初始位置。

調用clear()方法:

? ? ? ? position將被設回0,limit設置成capacity,換句話說,Buffer被清空了,其實Buffer中的數據并未被清楚,只是這些標記告訴我們可以從哪里開始往Buffer里寫數據。如果Buffer中有一些未讀的數據,調用clear()方法,數據將“被遺忘”,意味著不再有任何標記會告訴你哪些數據被讀過,哪些還沒有。如果Buffer中仍有未讀的數據,且后續還需要這些數據,但是此時想要先先寫些數據,那么使用compact()方法。

compact()方法:

? ? ? ? 將所有未讀的數據拷貝到Buffer起始處。然后將position設到最后一個未讀元素正后面。limit屬性依然像clear()方法一樣,設置成capacity?,F在Buffer準備好寫數據了,但是不會覆蓋未讀的數據。

通過調用Buffer.mark()方法:

? ? ? ? 可以標記Buffer中的一個特定的position,之后可以通過調用Buffer.reset()方法恢復到這個position。Buffer.rewind()方法將position設回0,所以你可以重讀Buffer中的所有數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素。

Selector

Selector運行單線程處理多個Channel,如果你的應用打開了多個通道,但每個連接的流量都很低,使用Selector就會很方便。例如在一個聊天服務器中。要使用Selector, 得向Selector注冊Channel,然后調用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新的連接進來、數據接收等。

? ? ? ? Selector的創建

? ? ? ? 通過調用Selector.open()方法創建一個Selector,如下:

? ? ? ? ? ? ? ? ? ? ? ? ? ?Selectorselector = Selector.open();

? ? ? ? 向Selector注冊通道

? ? ? ? 為了將Channel和Selector配合使用,必須將channel注冊到selector上。通過SelectableChannel.register()方法來實現,如下:


? ? ? ?與Selector一起使用時,Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用,因為FileChannel不能切換到非阻塞模式。而套接字通道都可以。

注意register()方法的第二個參數。這是一個“interest集合”,意思是在通過Selector監聽Channel時對什么事件感興趣。可以監聽四種不同類型的事件:

? ? ? ?Connect、Accept、Read、Write

? ? ? ? 通道觸發了一個事件意思是該事件已經就緒。所以,某個channel成功連接到另一個服務器稱為“連接就緒”。一個server socket

? ? ? ? channel準備好接收新進入的連接稱為“接收就緒”。一個有數據可讀的通道可以說是“讀就緒”。等待寫數據的通道可以說是“寫就緒”。

這四種事件用SelectionKey的四個常量來表示:

SelectionKey.OP_CONNECT

SelectionKey.OP_ACCEPT

SelectionKey.OP_READ

SelectionKey.OP_WRITE

如果你對不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來.


Netty架構按照Reactor模式設計和實現,它的服務端通信序列圖如下


? ? ?Netty的IO線程NioEventLoop由于聚合了多路復用器Selector,可以同時并發處理成百上千個客戶端Channel,由于讀寫操作都是非阻塞的,這就可以充分提升IO線程的運行效率,避免由于頻繁IO阻塞導致的線程掛起。另外,由于Netty采用了異步通信模式,一個IO線程可以并發處理N個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞IO一連接一線程模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升。

注:參考部分其他網站內容:

http://www.importnew.com/19816.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容