學習了一段時間的 Netty融虽,將重點與學習心得總結(jié)如下,本文主要總結(jié)ChannelHandler 及 EventLoop 的知識點和基本用法灼芭,本文章節(jié)排序參照《Netty in Action》的章節(jié)排序有额。
以下內(nèi)容主要參考「并發(fā)編程網(wǎng)」的 《Netty in Action》中文版 以及《Netty in Action》原版圖書,輔助參考 Essential Netty in Action 《Netty 實戰(zhàn)(精髓)》 以及 Netty 官網(wǎng)的 Netty 4.1 JavaDoc 。
6. ChannelHandler 和 ChannelPipeline
一個 Channel 正常的生命周期如下圖所示谆吴。隨著狀態(tài)發(fā)生變化倒源,相應(yīng)的 event 產(chǎn)生。這些 event 被轉(zhuǎn)發(fā)到 ChannelPipeline 中的 ChannelHandler 來采取相應(yīng)的操作句狼。
6.1 ChannelHandler
ChannelHandler 有兩個重要的子接口:
- 「ChannelInboundHandler」處理輸入數(shù)據(jù)和所有類型的狀態(tài)變化
- 「ChannelOutboundHandler」處理輸出數(shù)據(jù)笋熬,可以攔截所有操作
6.1.1 ChannelInboundHandler
下表列出接口 ChannelInboundHandler 的方法。當收到數(shù)據(jù)或相關(guān) Channel 的狀態(tài)改變時腻菇,這些方法被調(diào)用胳螟,這些方法和Channel的生命周期密切相關(guān)启具。
方法 | 描述 |
---|---|
channelRegistered | 當一個Channel注冊到EventLoop上层宫,可以處理I/O時被調(diào)用 |
channelUnregistered | 當一個Channel從它的EventLoop上解除注冊,不再處理I/O時被調(diào)用 |
channelActive | 當Channel變成活躍狀態(tài)時被調(diào)用蛛勉;Channel是連接/綁定丘薛、就緒的 |
channelInactive | 當Channel離開活躍狀態(tài)嘉竟,不再連接到某個遠端時被調(diào)用 |
channelReadComplete | 當Channel上的某個讀操作完成時被調(diào)用 |
channelRead | 當從Channel中讀數(shù)據(jù)時被調(diào)用 |
6.1.2 ChannelOutboundHandler
輸出的操作和數(shù)據(jù)由 ChannelOutBoundHandler 處理。它的方法可以被 Channel洋侨,ChannelPipeline 和 ChannelHandlerContext 調(diào)用舍扰,子接口 ChannelOutboundHandler 的主要方法如下:
方法 | 描述 |
---|---|
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) | 請求綁定 Channel 到一個本地地址 |
connect(ChannelHandlerContext, SocketAddress,SocketAddress,ChannelPromise) | 請求連接 Channel 到遠端 |
disconnect(ChannelHandlerContext, ChannelPromise) | 請求從遠端斷開 Channel |
close(ChannelHandlerContext,ChannelPromise) | 請求關(guān)閉 Channel |
deregister(ChannelHandlerContext, ChannelPromise) | 請求 Channel 從它的 EventLoop 上解除注冊 |
read(ChannelHandlerContext) | 請求從 Channel 中讀更多的數(shù)據(jù) |
flush(ChannelHandlerContext) | 請求通過 Channel 刷隊列數(shù)據(jù)到遠端 |
write(ChannelHandlerContext,Object, ChannelPromise) | 請求通過 Channel 寫數(shù)據(jù)到遠端 |
6.1.3 ChannelHandler 適配器類
ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 這兩個適配器類分別提供了 ChannelInboundHandler 和 ChannelOutboundHandler 的基本實現(xiàn),它們繼承了共同的父接口 ChannelHandler 的方法希坚,擴展了抽象類 ChannelHandlerAdapter边苹。
ChannelHandlerAdapter 提供了工具方法 isSharable()。如果類實現(xiàn)帶 @Sharable 注解裁僧,那么這個方法就會返回 true个束,意味著這個對象可以被添加到多個 ChannelPipeline 中。
ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 中的方法調(diào)用相關(guān) ChannelHandlerContext 中的等效方法聊疲,因此將事件轉(zhuǎn)發(fā)到管道中的下一個ChannelHandler茬底。
6.1.4 ChannelFuture 和 ChannelPromise
- ChannelPromise 是 ChannelFuture 的子接口
- 而 ChannelFuture 是不可變對象
- ChannelPromise 定義了可寫的方法,比如 setSuccess(), setFailure()
6.1.5 釋放資源
1. 輸入方向「Inbound」
當一個 ChannelInboundHandler 實現(xiàn)類重寫 channelRead() 方法時售睹,它要負責釋放 ByteBuf 相關(guān)的內(nèi)存桩警。可使用 Netty 提供的工具方法:
ReferenceCountUtil.release(「ByteBuf 的對象」)
更簡單的昌妹,可使用子類 SimpleChannelInboundHandler 捶枢,一條消息在被 ChannelRead0() 讀取后,會被自動釋放資源飞崖,此時任何對消息的引用都會變成無效烂叔,所以不能保存這些引用待后來使用。
2. 輸出方向「Outbound」
在輸出方向固歪,如果處理一個 write() 操作并且丟棄一條消息(沒有寫入 Channel)蒜鸡,就應(yīng)該負責釋放這條消息胯努。
@ChannelHandler.Sharable public
class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ReferenceCountUtil.release(msg); //使用 ReferenceCountUtil.release(...) 釋放資源
promise.setSuccess(); //通知 ChannelPromise 數(shù)據(jù)已經(jīng)被處理
}
如果一個消息被“消費”或者丟棄,沒有送到 ChannelPipeline 中的下一個 ChannelOutboundHandler逢防,用戶就要負責調(diào)用 ReferenceCountUtil.release()叶沛。如果消息到達了真正的傳輸層,在它被寫到 Socket 中或者 Channel 關(guān)閉時忘朝,會被自動釋放灰署,用戶不用管。
6.2 ChannelPipeline 接口
每個新創(chuàng)建的 Channel 都會分配一個新的 ChannelPipeline局嘁,Channel 不可以更換或解除當前的 ChannelPipeline溉箕,在 Netty 組件的整個生命周期中這個關(guān)系是固定的。
一個 ChannelPipeline 可看成是一串 ChannelHandler 實例悦昵,攔截穿過 Channel 的輸入輸出 event肴茄。
根據(jù)來源,一個 event 可以被一個 ChannelInboundHandler 或 ChannelOutboundHandler 處理但指。接下來寡痰,通過調(diào)用 ChannelHandlerContext 的方法,event 會被轉(zhuǎn)發(fā)到下一個同類型的 handler棋凳。
6.2.1 ChannelHandlerContext
通過 ChannelHandlerContext氓癌,一個 handler 可以通知 ChannelPipeline 中的下一個ChannelHandler,甚至動態(tài)改動下一個ChannelHandler 所屬的 ChannelPipeline贫橙。
ChannelPipeline 主要由一系列 ChannelHandler 組成的。ChannelPipeline 提供在 ChannelPipeline 中傳送 event 的方法反粥。
ChannelHandlerContext 的一些方法和其他類(Channel 和 ChannelPipeline)的方法名字相似卢肃,但是 ChannelHandlerContext 的方法采用了更短的 event 傳遞路程。我們應(yīng)該盡可能利用這一點來實現(xiàn)最好的性能才顿。
如果你在 Channel 或者 ChannelPipeline 實例上調(diào)用這些方法莫湘,它們的調(diào)用會穿過整個 pipeline。而在 ChannelHandlerContext 上調(diào)用的同樣的方法郑气,僅僅從當前 ChannelHandler 開始幅垮,走到 pipeline 中下一個可以處理這個 event 的 ChannelHandler。
「本節(jié)參考」 第六章 ChannelHandler 和 ChannelPipeline
7. EventLoop 和 EventLoopGroup
7.1 Java 基本的線程池模式
- 從池中空閑的線程中選出一個尾组,分配一個提交的task「一個Runnable的實現(xiàn)」
- 當task完成忙芒,線程返回池中,等待復用「下一次task分配」
7.2 EventLoop「事件循環(huán)」
- EventLoop 始終由一個線程驅(qū)動
- 一個 EventLoop 可以被指派來服務(wù)多個 Channel
- 一個 Channel 只擁有一個 EventLoop
task (Runnable或Callable) 可以直接提交到 EventLoop 實現(xiàn)即刻或者延后執(zhí)行讳侨。根據(jù)配置和可用的CPU核呵萨,可以創(chuàng)建多個 EventLoop 來優(yōu)化資源利用。
一個 event 的本質(zhì)決定了它將如何被處理跨跨;它可能從網(wǎng)絡(luò)協(xié)議棧傳送數(shù)據(jù)到你的應(yīng)用潮峦,或者反過來,或者做一些完全不一樣的事情。但是 event 處理邏輯必須足夠通用和靈活忱嘹,來對付所有可能的情況嘱腥。
所以,在 Netty 4拘悦,所有的 I/O 操作和 event 都是由分配給 EventLoop 的那一個 Thread 來處理的齿兔。Netty 4 采用的線程模型,在同一個線程的 EventLoop 中處理所有發(fā)生的事窄做。
7.3 EventLoopGroup
- EventLoopGroup 負責分配 EventLoop 到新創(chuàng)建的 Channel
- 異步實現(xiàn)只用了很少 EventLoop愧驱,這幾個 EventLoop 被所有 Channel 共享
- 一但 Channel 被指派了一個 EventLoop,在它的整個生命周期過程中椭盏,都會用這個 EventLoop
為 Channel 的 I/O 和 event 提供服務(wù)的 EventLoop 都包含在一個 EventLoopGroup 中组砚。EventLoop 創(chuàng)建和分配的方式根據(jù)傳輸實現(xiàn)的不同而有所不同。
異步實現(xiàn)只用了很少幾個 EventLoop(和它們關(guān)聯(lián)的線程)掏颊,在目前 Netty 的模型中糟红,這幾個 EventLoop 被所有 Channel 共享。這讓很多 Channel 被最少數(shù)量的線程服務(wù)乌叶,而不是每個 Channel 分配一個線程盆偿。
EventLoopGroup 負責分配一個 EventLoop 到每個新創(chuàng)建的 Channel。在目前的實現(xiàn)中准浴,采用循環(huán) (round-robin) 策略可以滿足一個平衡的分配事扭,同一個 Eventloop 還可能會被分配到多個 Channel。
「本節(jié)參考」 第七章 EventLoop和線程模型