Netty in action ——— 傳輸協議

本文是Netty文集中“Netty in action”系列的文章。主要是對Norman Maurer and Marvin Allen Wolfthal 的 《Netty in action》一書簡要翻譯,同時對重要點加上一些自己補充和擴展。

概要

  • OIO —— 阻塞傳輸
  • NIO —— 異步傳輸
  • Local transport —— JVM內部的異步通訊
  • Embedded transport —— 測試你的ChannelHandlers

數據流經一個網絡時總是有一樣的類型:字節。
使用JAVA提供OIO API 和 NIO API 有著很大的不同。
Netty使用了一個公共的API層,該API涵蓋了所以的傳輸實現

在Netty中使用OIO 和 NIO

通過Netty實現阻塞網絡(OIO)

通過Netty實現異步網絡(NIO)


傳輸協議API

傳輸API的關鍵是 Channel 接口,Channel接口被用于所有的I/O操作。


一個Channel會被分配有一個ChannelPipeline和一個ChannelConfig。
ChannelConfig持有所有設置Channel的配置并支持熱修改。因為一個指定的傳輸可能有它獨特的設置,它可以實現一個ChannelConfig的子類。
因為Channel都是獨一無二的,所以聲明Channel為java.lang.Comparable的子類用意是為了保證排序。因此,AbstractChannel對compareTo方法實現:當兩個不同的channel實例返回了相同的hashCode將拋出一個Error異常。
ChannelPipeline持有所以的ChannelHandler實例,這些ChannelHandler實例將被應用到入站和出站數據和事件上。這些ChannelHandlers實現了用于處理狀態改變和數據處理的應用邏輯。

典型的ChannelHandlers的使用包括:

  • 轉換數據格式從一種到另外一種
  • 提供異常的通知
  • 提供一個Channel活躍( active )或不活躍( inactive )的通知
  • 提供當一個Channel注冊( registered )到EventLoop或從EventLoop注銷( deregistered )的通知
  • 提供關于用戶定義事件的通知

Intercepting Filter :ChannelPipeline實現了一個常見的設計模式,攔截過濾器。UNIX 的管道是另一個常見的例子:指令被鏈接到一起,通過一個指令的輸出連接到下一個行的輸入。( 也就是將當前指令的輸出作為下一條指令的輸入內容,以此方式將指令給鏈接到一起 )

你可以通過需要添加或刪除ChannelHandler來即時修改ChannelPipeline。Netty的這個能力能被利用與構建一個高靈活性的應用。

Netty的Channel實現是線程安全的,所以你能夠存有一個Channel的引用,并在你需要的任何時候使用它去寫數據到遠端,甚至可以多個線程同時使用這個引用。

在多個線程中使用同個Channel
注意:消息將被保證按順序發送!

包含的傳輸協議

Netty提供的傳輸協議
NIO —— 非阻塞 I/O

NIO提供所有I/O操作的完全異步實現。它使用了基于selector的API。
selector的一個基本概念是作為一個注冊表,你請求收到一個通知當Channel的狀態改變時。
可能的狀態改變有:

  • OP_ACCEPT :個新Channel被接收并準備好 ( 服務端 )
  • OP_CONNECT :一個Channel連接已經完成 ( 客戶端 )
  • OP_READ :一個Channel的數據已經準備好被讀取
  • OP_WRITE :一個Channel的寫數據有效。
    OP_WRITE需要特別注意。該事件表示的是:請求收到通知,當Channel能夠寫入更多的數據時。這是當socket緩存已經完全滿的處理情況( 即,當socket緩存已經滿了,但還有數據未寫完時,需要注冊該事件為希望得到通知的事件 ),這經常發生在當數據的傳輸速度遠快于遠端處理數據的速度時。

在應用對狀態的改變作出反應后,selector將被重置,并且重復該過程。
這些模式被合并到一個指定的集合中,應用請求得到一個通知當該集合中包含的狀態改變時。

這些NIO的內部實現被用戶級API所隱藏,該API是Netty所有傳輸的共同實現。


零拷貝是目前僅適用于NIO和Epoll傳輸的功能。它允許你 快速且高效的移動數據從一個文件系統到網絡,而無需從內核空間拷貝數據到用戶空間,這能夠顯著提升如FTP 或 HTTP協議的性能。零拷貝功能并不是所有的操作系統都支持的。需要指明的零拷貝不能用于實現文件系統的數據加密或壓縮,它只能夠傳輸未加工的文件內容。相反的,傳輸一個已經被加密過的文件不是問題。
也就是說,有些文件系統不是單純的操作一個數據的傳輸,還要對文件進行一些加密和壓縮的操作,而這些需要將數據拷貝到用戶空間并對數據進行修改操作。所以像這樣的文件操作是不支持零拷貝的。

Epoll —— Linux的本地非阻塞傳輸

正如我們前面說展示的,Netty的NIO傳輸是基于java提供的異步/非阻塞網絡的通用抽象。盡管這確保了Netty的NIO能在任何平臺上使用;但它也有限制,因為JDK必須妥協才能讓所有的系統都具有相同的功能。
Linux作為日漸重要的高性能網絡平臺,這導致了許多先進功能的開發,包括epoll,一個高可擴展的I/O事件通知功能。
Netty為Linux提供了一個使用epoll的NIO API,通過該方式與你的設計更加一致并且使中斷的使用成本更低。在大負載的性能上,Linux NIO 實現優于JDK NIO 的實現。

OIO —— 老的阻塞 I/O

Netty OIO傳輸實現代表著一種妥協:它通過通用的傳輸API來訪問,但因為他構建在java.net的阻塞實現上,它是非異步的。它非常適用于某些情況。

鑒于此,你可能擔心Netty如何提供一個NIO通過一樣的API用于異步的傳輸。這個答案是Netty使用 SO_TIMEOUT Socket 標志,該標志指定了等待I/O操作完成的最大毫秒數。如果一個操作在指定期間內沒有完成,那么將拋出一個SocketTimeoutException異常。Netty捕獲這個異常并繼續處理循環。在下一次EventLoop運行時,將再嘗試一次前面的邏輯。這是一個像Netty的異步框架能夠支持OIO的唯一方式。

我們通過OioSocketChannel的讀操作來了解下關于上面描述的源碼實現:


??這個讀操操作如果拋出超時異常,則會返回讀到的字節數為0。這里大家可以關注另外一點,在當socket關閉是,返回時可讀字節數為-1。這個是和NIO的模式相一致的,在NIO中如果read返回的可讀字節數為-1時,也就表示當遠端連接已經關閉了。

用于JVM內部通訊的本地傳輸

Netty提供了一個本地傳輸用于客戶端和服務端在相同JVM的異步通訊。
在該傳輸中,一個同服務端Channel關聯的SocketAddress不會綁定到一個物理網絡地址;當然,它會被保存到一個注冊表在服務端運行的期間,并在Channel ( 這里指服務端的channel )關閉時被注銷。所以傳輸沒有通過真實的網絡傳輸,所以它不能通過其他傳輸的實現來進行交互 ( 也就是不能同其他傳輸,如NIO transport 進行數據的傳輸交互 )。
所以客戶端希望連接一個在同一JVM的使用了該傳輸方式的服務端,那么客戶端也需要使用該傳輸方式。除了這個限制,它與其他傳輸方式并無不同。

內嵌的傳輸協議

Netty提供了一個附加的傳輸方式,該傳輸方式允許你一個ChannelHandler作為輔助類嵌入到其他ChannelHandler中。照這樣,你能在不修改內部代碼的情況下夠擴展一個ChannelHandler的功能。

EmbeddedChannel 允許你一個ChannelHandler作為輔助類嵌入到其他ChannelHandler中的方式類似如下:
這樣就可以傳入輔助channelHandler和原channelHandler,得到一個嵌套的channelHandler

傳輸協議使用場景

并不是所有的傳輸方式都支持所有的傳輸協議。

這里是你可能會遇到的使用場景:

  • 非阻塞代碼庫 —— 如果你不要一個阻塞調用在你的代碼庫中,或者你能夠限制它們,在Linux上使用NIO或epoll經常是個好主意。當NIO/epoll 用于處理許多并發的連接,它也能通過更少的線程來更好的工作,尤其是在連接間共享線程的方式。
  • 阻塞代碼庫 —— 正如我們已經說到的,如果你的代碼庫嚴重依賴于阻塞I/O,并且你的應用有對應于此的設計。如果你直接轉為Netty的NIO傳輸方式,你可能會遇到阻塞操作問題。對比與重寫你的代碼去完成這些,考慮一個階段性的遷移:從OIO開始,然后轉移到NIO(或epoll如果你在Linux上)當你改進你的代碼后。
  • 相同JVM的內部通訊 —— 在相同JVM的內部通訊不需要暴露一個服務在網絡表現層,在相同JVM的內部通訊為本地傳輸的完美使用情況。這將消除真實網絡操作的所有開銷,同時仍然使用你的Netty代碼庫。如果需要暴露一個服務在網絡上,你只需要簡單的修改傳輸方式為NIO或OIO。
  • 測試你的ChannelHandler的實現 —— 如果你想要寫單元測試用于你的ChannelHandler實現,考慮使用內嵌的傳輸方式。這將使測試你的代碼變得簡單,而不需創建許多的mock對象。你的類將仍然遵循通用API的事件流,保證ChannelHandler將在真實傳輸中正確工作。


后記

本文主要對Netty的支持的傳輸協議進行了介紹。即便是不同的傳輸協議,Netty也為我們提供了一致的API接口,它將大量復雜的處理邏輯封裝在了源碼實現中,為用戶提供了簡易且方便的API接口,這也是Netty設計一致性的例子之一。
若文章有任何錯誤,望大家不吝指教:)

參考

《Netty in action》

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

推薦閱讀更多精彩內容