點(diǎn)擊查看 《Netty in Action》筆記目錄。
本文是對《Netty in Action》第3章內(nèi)容的筆記和翻譯区拳,主要內(nèi)容包括:
- Netty 技術(shù)和架構(gòu)方面的介紹
-
Channel
浅蚪、EventLoop
和ChannelFuture
-
ChannelHandler
和ChannelPipeline
- 啟動( Bootstrapping)
Channel、EventLoop 和 ChannelFuture
下面這些可以被看做 Netty 網(wǎng)絡(luò)模型的抽象:
-
Channel
:Sockets
-
EventLoop
:流控烫罩、多線程惜傲、并發(fā) -
ChannelFuture
:異步通知
Channel 接口
Java 中基本的網(wǎng)絡(luò) I/O 操作依賴 Socket
類,如:bind()
贝攒、connect()
盗誊、read()
和 write()
。Netty 中的 Channel
接口提供的 API 與直接操作 Sockets
API 相比隘弊,大大減少了復(fù)雜度哈踱。此外,Channel
是很多專有實(shí)現(xiàn)的基類梨熙,這些實(shí)現(xiàn)包括:
EmbeddedChannel
LocalServerChannel
NioDatagramChannel
NioSctpChannel
NioSocketChannel
EventLoop 接口
EventLoop
定義了 Netty 核心抽象:在一個連接周期中處理發(fā)生的事件开镣。圖3.1 在一個高層次展示了 Channels
、EventLoops
咽扇、Threads
和 EventLoopGroups
的關(guān)系邪财。關(guān)系如下:
- 一個
EventLoopGroup
包括了一個或多個EventLoop
陕壹。 - 一個
EventLoop
在生命周期中只會綁定到一個單一的線程Thread
。 - 所有被
EventLoop
處理的 I/O 事件都會被它綁定的線程Thread
處理树埠。 - 一個
Channel
會在一個單一的EventLoop
中注冊它的生命周期糠馆。 - 一個單一的
EventLoop
可能會被分配多個Channels
。
值得注意的是:在這個設(shè)計(jì)中又碌,一個給定 Channel
的 I/O 的操作都是由同一個線程執(zhí)行的,也就是這些操作是單線程的绊袋,所以無形中消除了同步的問題毕匀。
ChannelFuture 接口
Netty 中提供了 ChannelFuture
,這個接口具有 addListener()
方法愤炸,通過這個方法可以注冊一個 ChannelFutureListener
期揪,這個監(jiān)聽器會在方法結(jié)束時(shí)被調(diào)用(無論執(zhí)行是否成功)。
ChannelFuture 的更多介紹
可以把ChannelFuture
看做是存放結(jié)果的一個站位空間规个,這里的結(jié)果將會在未來的某個時(shí)刻被填滿凤薛。可以確定的是:對于同一個Channel
的所有操作都會被執(zhí)行诞仓,并且執(zhí)行順序與調(diào)用的順序一致缤苫。
ChannelHandler 和 ChannelPipeline
ChannelHandler 接口
對于應(yīng)用開發(fā)者來說,Netty 中最重要的組件是 ChannelHandler
墅拭。這個組件承載了對輸入輸出數(shù)據(jù)的所有應(yīng)用處理邏輯活玲。
事實(shí)上,ChannelHandler
幾乎可以滿足所有的操作谍婉,包括:轉(zhuǎn)換數(shù)據(jù)的格式舒憾、處理過程中拋出的異常等。
ChannelPipeline 接口
ChannelPipeline
提供了鏈?zhǔn)?ChannelHandlers
的一個承載容器穗熬,并且定義了在這個鏈中傳遞輸入輸出事件的 API镀迂。當(dāng)一個 Channel
被創(chuàng)建的時(shí)候,它會被自動綁定到它所在的 ChannelPipeline
上唤蔗。
ChannelHandler
通過以下步驟安裝到 ChannelPipeline
上:
- 在
ServerBootstrap
上注冊一個ChannelInitializer
的實(shí)現(xiàn)探遵。 - 當(dāng)
ChannelInitializer.initChannel()
函數(shù)被調(diào)用的時(shí)候,ChannelInitializer
將會在 pipeline 中安裝一系列的ChannelHandler
妓柜。 - 然后
ChannelInitializer
從ChannelPipeline
中把它自身刪除箱季。
圖3.2 展示了從 ChannelHandler
派生出來的 ChannelInboundHandler
和 ChannelOutboundHandler
。
圖3.3 展示了 Netty 應(yīng)用中輸入和輸出端數(shù)據(jù)流的區(qū)別棍掐。
輸入輸出處理器的更多介紹
一個事件可以通過ChannelHandlerContext
傳遞到鏈中的下一個處理器藏雏,ChannelHandlerContext
在每個方法中都會被作為一個輸入?yún)?shù)。因?yàn)槟阌袝r(shí)候會忽略不感興趣的事件作煌,Netty 提供了抽象基礎(chǔ)類ChannelInboundHandlerAdapter
和ChannelOutboundHandlerAdapter
诉稍。每個都提供了方法實(shí)現(xiàn)蝠嘉,通過調(diào)用ChannelHandlerContext
中的相應(yīng)方法,可以簡化傳遞到下一個處理者的操作杯巨。然后蚤告,你可以擴(kuò)展類并覆蓋你感興趣的方法。
當(dāng)兩種類別處理器在同一個 ChannelPipeline
中混合時(shí)會發(fā)生什么服爷?盡管輸入輸出 handler 都繼承于 ChannelHandler
杜恰,但 Netty 還是會區(qū)分 ChannelInboundHandler
和 ChannelOutboundHandler
的不同實(shí)現(xiàn),并且 確保數(shù)據(jù)只在方向相同類型的 handler 之間傳遞仍源。
當(dāng) ChannelHandler
被加入到 ChannelPipeline
中時(shí)心褐,它會被分配一個 ChannelHandlerContext
,這個 ChannelHandlerContext
代表了 ChannelHandler
和 ChannelPipeline
之間的綁定關(guān)系笼踩。
Netty 中發(fā)送數(shù)據(jù)的方法有兩種:
- 直接寫入到
Channel
逗爹,消息會從ChannelPipeline
的尾部開始傳遞。 - 寫入到
ChannelHandler
關(guān)聯(lián)的ChannelHandlerContext
嚎于,消息會從ChannelPipeline
中的__下一個__handler 開始傳遞掘而。
進(jìn)一步了解 ChannelHandler
Netty 中有一些適配類,從而減少了常用 ChannelHandler
類的工作量于购。這些適配類提供了相應(yīng)接口方法的默認(rèn)實(shí)現(xiàn)袍睡。
你在寫 handle 時(shí)經(jīng)常會用到的適配類包括:
ChannelHandlerAdapter
ChannelInboundHandlerAdapter
ChannelOutboundHandlerAdapter
ChannelDuplexHandlerAdapter
編碼和解碼
當(dāng)你使用 Netty 發(fā)送和接收數(shù)據(jù)時(shí),需要進(jìn)行數(shù)據(jù)格式的轉(zhuǎn)換肋僧。輸入的消息將會被解碼斑胜,也就是將輸入的字節(jié)轉(zhuǎn)換為另一種格式,典型的是轉(zhuǎn)化為一個 Java 對象嫌吠。如果數(shù)據(jù)是輸出的止潘,相反的操作將會發(fā)生:會從現(xiàn)在的格式編碼成自己。需要這樣做的原因很簡單辫诅,網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)是一系列的字節(jié)凭戴。
所有 Netty 提供的編碼解碼(encoder/decoder)的適配類都實(shí)現(xiàn)于 ChannelInboundHandler
或者 ChannelOutboundHandler
。
你將發(fā)現(xiàn):對于輸入數(shù)據(jù)泥栖,channelRead
方法或事件會被覆寫簇宽。對于輸入 Channel
的每條數(shù)據(jù)勋篓,這個方法都會被調(diào)用吧享。然后會調(diào)用解碼器的 decode()
方法,并將解碼后的字節(jié)傳輸?shù)?pipeline 中的下一個 ChannelInboundHandler
譬嚣。
而輸出數(shù)據(jù)的模式正好相反:編碼器將消息轉(zhuǎn)換為字節(jié)钢颂,并將這些字節(jié)傳遞到下一個 ChannelOutboundHandler
。
抽象類 SimpleChannelInboundHandler
很常見的是拜银,你的應(yīng)用需要實(shí)現(xiàn)一個 handler 來接受解碼的數(shù)據(jù)并且在數(shù)據(jù)上實(shí)現(xiàn)應(yīng)用邏輯殊鞭。創(chuàng)建這樣的一個 ChannelHandler
遭垛,你只需要擴(kuò)展基類 SimpleChannelInboundHandler<T>
,其中 T
是你需要處理的數(shù)據(jù)對應(yīng)的 Java 類型操灿。
這個 handler 中最重要的一個方法是 channelRead0(ChannelHandlerContext,T)
锯仪。
Bootstrapping
Netty 的啟動類可以為應(yīng)用的網(wǎng)絡(luò)層提供配置容器。
面向連接的協(xié)議
請記字貉巍:嚴(yán)格意義上的連接(“connection”)只適用于面向連接的協(xié)議庶喜,如 TCP。在這樣的協(xié)議中救鲤,可以保證連接的端之間可以按序接收到數(shù)據(jù)久窟。
相應(yīng)的,有兩種類型的啟動類:一個是客戶端(Bootstrap
)本缠,另一個是服務(wù)端(ServerBootstrap
)斥扛。
下表對這兩種類型的啟動進(jìn)行了比較。
類別 | Bootstrap | ServerBootstrap |
---|---|---|
網(wǎng)絡(luò)功能 | 連接遠(yuǎn)程主機(jī)和端口 | 綁定本地端口 |
EventLoopGroup 的數(shù)目 |
1 | 2 |
啟動一個客戶端只需要一個 EventLoopGroup
丹锹,但是服務(wù)端的 ServerBootstrap
需要兩個(可以是同一個實(shí)例)稀颁,這是為什么?
服務(wù)端需要兩個區(qū)分的 Channel
集合卷仑。第一個集合包括一個 ServerChannel
峻村,它代表了服務(wù)端自己用于監(jiān)聽(listening)的 socket,綁定到本地端口锡凝;第二個集合包括了所有創(chuàng)建的 Channels
粘昨,用于處理輸入服務(wù)端接收到的各個客戶端連接。圖3.4 展示了這個模型窜锯,并說明了為什么需要2個不同的 EventLoopGroups
张肾。
與 ServerChannel
相關(guān)的 EventLoopGroup
為 ServerChannel
分配了一個 EventLoop
,這個 EventLoop
負(fù)責(zé)為即將到來的連接請求創(chuàng)建 Channel
锚扎。一旦接收到連接吞瞪,第二個 EventLoopGroup
會為創(chuàng)建出來的 Channel
分配對應(yīng)的 EventLoop
。