Netty in action ——— 傳輸協(xié)議

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

概要

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

數(shù)據(jù)流經(jīng)一個網(wǎng)絡(luò)時總是有一樣的類型:字節(jié)。
使用JAVA提供OIO API 和 NIO API 有著很大的不同。
Netty使用了一個公共的API層蹈矮,該API涵蓋了所以的傳輸實現(xiàn)

在Netty中使用OIO 和 NIO

通過Netty實現(xiàn)阻塞網(wǎng)絡(luò)(OIO)

通過Netty實現(xiàn)異步網(wǎng)絡(luò)(NIO)


傳輸協(xié)議API

傳輸API的關(guān)鍵是 Channel 接口砰逻,Channel接口被用于所有的I/O操作。


一個Channel會被分配有一個ChannelPipeline和一個ChannelConfig泛鸟。
ChannelConfig持有所有設(shè)置Channel的配置并支持熱修改蝠咆。因為一個指定的傳輸可能有它獨(dú)特的設(shè)置,它可以實現(xiàn)一個ChannelConfig的子類北滥。
因為Channel都是獨(dú)一無二的刚操,所以聲明Channel為java.lang.Comparable的子類用意是為了保證排序。因此再芋,AbstractChannel對compareTo方法實現(xiàn):當(dāng)兩個不同的channel實例返回了相同的hashCode將拋出一個Error異常菊霜。
ChannelPipeline持有所以的ChannelHandler實例,這些ChannelHandler實例將被應(yīng)用到入站和出站數(shù)據(jù)和事件上济赎。這些ChannelHandlers實現(xiàn)了用于處理狀態(tài)改變和數(shù)據(jù)處理的應(yīng)用邏輯占卧。

典型的ChannelHandlers的使用包括:

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

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

你可以通過需要添加或刪除ChannelHandler來即時修改ChannelPipeline。Netty的這個能力能被利用與構(gòu)建一個高靈活性的應(yīng)用贺拣。

Netty的Channel實現(xiàn)是線程安全的蓖谢,所以你能夠存有一個Channel的引用,并在你需要的任何時候使用它去寫數(shù)據(jù)到遠(yuǎn)端譬涡,甚至可以多個線程同時使用這個引用闪幽。

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

包含的傳輸協(xié)議

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

NIO提供所有I/O操作的完全異步實現(xiàn)涡匀。它使用了基于selector的API盯腌。
selector的一個基本概念是作為一個注冊表,你請求收到一個通知當(dāng)Channel的狀態(tài)改變時陨瘩。
可能的狀態(tài)改變有:

  • OP_ACCEPT :個新Channel被接收并準(zhǔn)備好 ( 服務(wù)端 )
  • OP_CONNECT :一個Channel連接已經(jīng)完成 ( 客戶端 )
  • OP_READ :一個Channel的數(shù)據(jù)已經(jīng)準(zhǔn)備好被讀取
  • OP_WRITE :一個Channel的寫數(shù)據(jù)有效腕够。
    OP_WRITE需要特別注意。該事件表示的是:請求收到通知舌劳,當(dāng)Channel能夠?qū)懭敫嗟臄?shù)據(jù)時帚湘。這是當(dāng)socket緩存已經(jīng)完全滿的處理情況( 即,當(dāng)socket緩存已經(jīng)滿了甚淡,但還有數(shù)據(jù)未寫完時大诸,需要注冊該事件為希望得到通知的事件 ),這經(jīng)常發(fā)生在當(dāng)數(shù)據(jù)的傳輸速度遠(yuǎn)快于遠(yuǎn)端處理數(shù)據(jù)的速度時。

在應(yīng)用對狀態(tài)的改變作出反應(yīng)后资柔,selector將被重置焙贷,并且重復(fù)該過程。
這些模式被合并到一個指定的集合中建邓,應(yīng)用請求得到一個通知當(dāng)該集合中包含的狀態(tài)改變時盈厘。

這些NIO的內(nèi)部實現(xiàn)被用戶級API所隱藏,該API是Netty所有傳輸?shù)墓餐瑢崿F(xiàn)官边。


零拷貝是目前僅適用于NIO和Epoll傳輸?shù)墓δ芊惺帧K试S你 快速且高效的移動數(shù)據(jù)從一個文件系統(tǒng)到網(wǎng)絡(luò),而無需從內(nèi)核空間拷貝數(shù)據(jù)到用戶空間注簿,這能夠顯著提升如FTP 或 HTTP協(xié)議的性能契吉。零拷貝功能并不是所有的操作系統(tǒng)都支持的。需要指明的零拷貝不能用于實現(xiàn)文件系統(tǒng)的數(shù)據(jù)加密或壓縮诡渴,它只能夠傳輸未加工的文件內(nèi)容捐晶。相反的,傳輸一個已經(jīng)被加密過的文件不是問題妄辩。
也就是說惑灵,有些文件系統(tǒng)不是單純的操作一個數(shù)據(jù)的傳輸,還要對文件進(jìn)行一些加密和壓縮的操作眼耀,而這些需要將數(shù)據(jù)拷貝到用戶空間并對數(shù)據(jù)進(jìn)行修改操作英支。所以像這樣的文件操作是不支持零拷貝的。

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

正如我們前面說展示的哮伟,Netty的NIO傳輸是基于java提供的異步/非阻塞網(wǎng)絡(luò)的通用抽象干花。盡管這確保了Netty的NIO能在任何平臺上使用;但它也有限制楞黄,因為JDK必須妥協(xié)才能讓所有的系統(tǒng)都具有相同的功能池凄。
Linux作為日漸重要的高性能網(wǎng)絡(luò)平臺,這導(dǎo)致了許多先進(jìn)功能的開發(fā)鬼廓,包括epoll肿仑,一個高可擴(kuò)展的I/O事件通知功能。
Netty為Linux提供了一個使用epoll的NIO API碎税,通過該方式與你的設(shè)計更加一致并且使中斷的使用成本更低柏副。在大負(fù)載的性能上,Linux NIO 實現(xiàn)優(yōu)于JDK NIO 的實現(xiàn)蚣录。

OIO —— 老的阻塞 I/O

Netty OIO傳輸實現(xiàn)代表著一種妥協(xié):它通過通用的傳輸API來訪問割择,但因為他構(gòu)建在java.net的阻塞實現(xiàn)上,它是非異步的萎河。它非常適用于某些情況荔泳。

鑒于此蕉饼,你可能擔(dān)心Netty如何提供一個NIO通過一樣的API用于異步的傳輸。這個答案是Netty使用 SO_TIMEOUT Socket 標(biāo)志玛歌,該標(biāo)志指定了等待I/O操作完成的最大毫秒數(shù)昧港。如果一個操作在指定期間內(nèi)沒有完成,那么將拋出一個SocketTimeoutException異常支子。Netty捕獲這個異常并繼續(xù)處理循環(huán)创肥。在下一次EventLoop運(yùn)行時,將再嘗試一次前面的邏輯值朋。這是一個像Netty的異步框架能夠支持OIO的唯一方式叹侄。

我們通過OioSocketChannel的讀操作來了解下關(guān)于上面描述的源碼實現(xiàn):


??這個讀操操作如果拋出超時異常,則會返回讀到的字節(jié)數(shù)為0昨登。這里大家可以關(guān)注另外一點趾代,在當(dāng)socket關(guān)閉是,返回時可讀字節(jié)數(shù)為-1丰辣。這個是和NIO的模式相一致的撒强,在NIO中如果read返回的可讀字節(jié)數(shù)為-1時,也就表示當(dāng)遠(yuǎn)端連接已經(jīng)關(guān)閉了笙什。

用于JVM內(nèi)部通訊的本地傳輸

Netty提供了一個本地傳輸用于客戶端和服務(wù)端在相同JVM的異步通訊飘哨。
在該傳輸中,一個同服務(wù)端Channel關(guān)聯(lián)的SocketAddress不會綁定到一個物理網(wǎng)絡(luò)地址琐凭;當(dāng)然芽隆,它會被保存到一個注冊表在服務(wù)端運(yùn)行的期間,并在Channel ( 這里指服務(wù)端的channel )關(guān)閉時被注銷淘正。所以傳輸沒有通過真實的網(wǎng)絡(luò)傳輸,所以它不能通過其他傳輸?shù)膶崿F(xiàn)來進(jìn)行交互 ( 也就是不能同其他傳輸臼闻,如NIO transport 進(jìn)行數(shù)據(jù)的傳輸交互 )鸿吆。
所以客戶端希望連接一個在同一JVM的使用了該傳輸方式的服務(wù)端,那么客戶端也需要使用該傳輸方式述呐。除了這個限制惩淳,它與其他傳輸方式并無不同。

內(nèi)嵌的傳輸協(xié)議

Netty提供了一個附加的傳輸方式乓搬,該傳輸方式允許你一個ChannelHandler作為輔助類嵌入到其他ChannelHandler中思犁。照這樣,你能在不修改內(nèi)部代碼的情況下夠擴(kuò)展一個ChannelHandler的功能进肯。

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

傳輸協(xié)議使用場景

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

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

  • 非阻塞代碼庫 —— 如果你不要一個阻塞調(diào)用在你的代碼庫中江掩,或者你能夠限制它們学辱,在Linux上使用NIO或epoll經(jīng)常是個好主意乘瓤。當(dāng)NIO/epoll 用于處理許多并發(fā)的連接,它也能通過更少的線程來更好的工作策泣,尤其是在連接間共享線程的方式衙傀。
  • 阻塞代碼庫 —— 正如我們已經(jīng)說到的,如果你的代碼庫嚴(yán)重依賴于阻塞I/O萨咕,并且你的應(yīng)用有對應(yīng)于此的設(shè)計统抬。如果你直接轉(zhuǎn)為Netty的NIO傳輸方式,你可能會遇到阻塞操作問題危队。對比與重寫你的代碼去完成這些聪建,考慮一個階段性的遷移:從OIO開始,然后轉(zhuǎn)移到NIO(或epoll如果你在Linux上)當(dāng)你改進(jìn)你的代碼后交掏。
  • 相同JVM的內(nèi)部通訊 —— 在相同JVM的內(nèi)部通訊不需要暴露一個服務(wù)在網(wǎng)絡(luò)表現(xiàn)層妆偏,在相同JVM的內(nèi)部通訊為本地傳輸?shù)耐昝朗褂们闆r。這將消除真實網(wǎng)絡(luò)操作的所有開銷盅弛,同時仍然使用你的Netty代碼庫钱骂。如果需要暴露一個服務(wù)在網(wǎng)絡(luò)上,你只需要簡單的修改傳輸方式為NIO或OIO挪鹏。
  • 測試你的ChannelHandler的實現(xiàn) —— 如果你想要寫單元測試用于你的ChannelHandler實現(xiàn)见秽,考慮使用內(nèi)嵌的傳輸方式。這將使測試你的代碼變得簡單讨盒,而不需創(chuàng)建許多的mock對象解取。你的類將仍然遵循通用API的事件流,保證ChannelHandler將在真實傳輸中正確工作返顺。


后記

本文主要對Netty的支持的傳輸協(xié)議進(jìn)行了介紹禀苦。即便是不同的傳輸協(xié)議,Netty也為我們提供了一致的API接口遂鹊,它將大量復(fù)雜的處理邏輯封裝在了源碼實現(xiàn)中振乏,為用戶提供了簡易且方便的API接口,這也是Netty設(shè)計一致性的例子之一秉扑。
若文章有任何錯誤慧邮,望大家不吝指教:)

參考

《Netty in action》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舟陆,隨后出現(xiàn)的幾起案子误澳,更是在濱河造成了極大的恐慌,老刑警劉巖秦躯,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忆谓,死亡現(xiàn)場離奇詭異,居然都是意外死亡踱承,警方通過查閱死者的電腦和手機(jī)陪毡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門米母,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人毡琉,你說我怎么就攤上這事铁瞒。” “怎么了桅滋?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵慧耍,是天一觀的道長。 經(jīng)常有香客問我丐谋,道長芍碧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任号俐,我火速辦了婚禮泌豆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吏饿。我一直安慰自己踪危,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布猪落。 她就那樣靜靜地躺著贞远,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笨忌。 梳的紋絲不亂的頭發(fā)上蓝仲,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音官疲,去河邊找鬼袱结。 笑死,一個胖子當(dāng)著我的面吹牛途凫,可吹牛的內(nèi)容都是我干的垢夹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颖榜,長吁一口氣:“原來是場噩夢啊……” “哼棚饵!你這毒婦竟也來了煤裙?” 一聲冷哼從身側(cè)響起掩完,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蹈胡,失蹤者是張志新(化名)和其女友劉穎登舞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體混卵,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡题翰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年恶阴,在試婚紗的時候發(fā)現(xiàn)自己被綠了诈胜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡冯事,死狀恐怖焦匈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昵仅,我是刑警寧澤缓熟,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站摔笤,受9級特大地震影響够滑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吕世,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一彰触、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧命辖,春花似錦况毅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漓帚,卻和暖如春母债,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尝抖。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工毡们, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昧辽。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓衙熔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搅荞。 傳聞我的和親對象是個殘疾皇子红氯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容