「部落格搬家」 原地址: 簡書 原發表時間: 2017-05-05
學習了一段時間的 Netty,將重點與學習心得總結如下,本文主要總結ChannelHandler 及 EventLoop 的知識點和基本用法,本文章節排序參照《Netty in Action》的章節排序。
以下內容主要參考「併發程式設計網」的 《Netty in Action》中文版 以及《Netty in Action》原版圖書,輔助參考 Essential Netty in Action 《Netty 實戰(精髓)》 以及 Netty 官網的 Netty 4.1 JavaDoc 。
6. ChannelHandler 和 ChannelPipeline
一個 Channel 正常的生命週期如下圖所示。隨著狀態發生變化,相應的 event 產生。這些 event 被轉發到 ChannelPipeline 中的 ChannelHandler 來採取相應的操作。
6.1 ChannelHandler
ChannelHandler 有兩個重要的子介面:
- 「ChannelInboundHandler」處理輸入資料和所有型別的狀態變化
- 「ChannelOutboundHandler」處理輸出資料,可以攔截所有操作
6.1.1 ChannelInboundHandler
下表列出介面 ChannelInboundHandler 的方法。當收到資料或相關 Channel 的狀態改變時,這些方法被呼叫,這些方法和Channel的生命週期密切相關。
方法 | 描述 |
---|---|
channelRegistered | 當一個Channel註冊到EventLoop上,可以處理I/O時被呼叫 |
channelUnregistered | 當一個Channel從它的EventLoop上解除註冊,不再處理I/O時被呼叫 |
channelActive | 當Channel變成活躍狀態時被呼叫;Channel是連線/繫結、就緒的 |
channelInactive | 當Channel離開活躍狀態,不再連線到某個遠端時被呼叫 |
channelReadComplete | 當Channel上的某個讀操作完成時被呼叫 |
channelRead | 當從Channel中讀資料時被呼叫 |
6.1.2 ChannelOutboundHandler
輸出的操作和資料由 ChannelOutBoundHandler 處理。它的方法可以被 Channel,ChannelPipeline 和 ChannelHandlerContext 呼叫,子介面 ChannelOutboundHandler 的主要方法如下:
方法 | 描述 |
---|---|
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) | 請求繫結 Channel 到一個本地地址 |
connect(ChannelHandlerContext, SocketAddress,SocketAddress,ChannelPromise) | 請求連線 Channel 到遠端 |
disconnect(ChannelHandlerContext, ChannelPromise) | 請求從遠端斷開 Channel |
close(ChannelHandlerContext,ChannelPromise) | 請求關閉 Channel |
deregister(ChannelHandlerContext, ChannelPromise) | 請求 Channel 從它的 EventLoop 上解除註冊 |
read(ChannelHandlerContext) | 請求從 Channel 中讀更多的資料 |
flush(ChannelHandlerContext) | 請求通過 Channel 刷佇列資料到遠端 |
write(ChannelHandlerContext,Object, ChannelPromise) | 請求通過 Channel 寫資料到遠端 |
6.1.3 ChannelHandler 介面卡類
ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 這兩個介面卡類分別提供了 ChannelInboundHandler 和 ChannelOutboundHandler 的基本實現,它們繼承了共同的父介面 ChannelHandler 的方法,擴充套件了抽象類 ChannelHandlerAdapter。
-
ChannelHandlerAdapter 提供了工具方法 isSharable()。如果類實現帶 @Sharable 註解,那麼這個方法就會返回 true,意味著這個物件可以被新增到多個 ChannelPipeline 中。
-
ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 中的方法呼叫相關 ChannelHandlerContext 中的等效方法,因此將事件轉發到管道中的下一個ChannelHandler。
6.1.4 ChannelFuture 和 ChannelPromise
- ChannelPromise 是 ChannelFuture 的子介面
- 而 ChannelFuture 是不可變物件
- ChannelPromise 定義了可寫的方法,比如 setSuccess(), setFailure()
6.1.5 釋放資源
1. 輸入方向「Inbound」 當一個 ChannelInboundHandler 實現類重寫 channelRead() 方法時,它要負責釋放 ByteBuf 相關的記憶體。可使用 Netty 提供的工具方法:
ReferenceCountUtil.release(「ByteBuf 的物件」)
複製程式碼
更簡單的,可使用子類 SimpleChannelInboundHandler ,一條訊息在被 ChannelRead0() 讀取後,會被自動釋放資源,此時任何對訊息的引用都會變成無效,所以不能儲存這些引用待後來使用。
2. 輸出方向「Outbound」 在輸出方向,如果處理一個 write() 操作並且丟棄一條訊息(沒有寫入 Channel),就應該負責釋放這條訊息。
@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 資料已經被處理
}
複製程式碼
如果一個訊息被“消費”或者丟棄,沒有送到 ChannelPipeline 中的下一個 ChannelOutboundHandler,使用者就要負責呼叫 ReferenceCountUtil.release()。如果訊息到達了真正的傳輸層,在它被寫到 Socket 中或者 Channel 關閉時,會被自動釋放,使用者不用管。
6.2 ChannelPipeline 介面
-
每個新建立的 Channel 都會分配一個新的 ChannelPipeline,Channel 不可以更換或解除當前的 ChannelPipeline,在 Netty 元件的整個生命週期中這個關係是固定的。
-
一個 ChannelPipeline 可看成是一串 ChannelHandler 例項,攔截穿過 Channel 的輸入輸出 event。
-
根據來源,一個 event 可以被一個 ChannelInboundHandler 或 ChannelOutboundHandler 處理。接下來,通過呼叫 ChannelHandlerContext 的方法,event 會被轉發到下一個同型別的 handler。
6.2.1 ChannelHandlerContext
-
通過 ChannelHandlerContext,一個 handler 可以通知 ChannelPipeline 中的下一個ChannelHandler,甚至動態改動下一個ChannelHandler 所屬的 ChannelPipeline。
-
ChannelPipeline 主要由一系列 ChannelHandler 組成的。ChannelPipeline 提供在 ChannelPipeline 中傳送 event 的方法。
-
ChannelHandlerContext 的一些方法和其他類(Channel 和 ChannelPipeline)的方法名字相似,但是 ChannelHandlerContext 的方法採用了更短的 event 傳遞路程。我們應該儘可能利用這一點來實現最好的效能。
-
如果你在 Channel 或者 ChannelPipeline 例項上呼叫這些方法,它們的呼叫會穿過整個 pipeline。而在 ChannelHandlerContext 上呼叫的同樣的方法,僅僅從當前 ChannelHandler 開始,走到 pipeline 中下一個可以處理這個 event 的 ChannelHandler。
「本節參考」 第六章 ChannelHandler 和 ChannelPipeline
7. EventLoop 和 EventLoopGroup
7.1 Java 基本的執行緒池模式
- 從池中空閒的執行緒中選出一個,分配一個提交的task「一個Runnable的實現」
- 當task完成,執行緒返回池中,等待複用「下一次task分配」
7.2 EventLoop「事件迴圈」
- EventLoop 始終由一個執行緒驅動
- 一個 EventLoop 可以被指派來服務多個 Channel
- 一個 Channel 只擁有一個 EventLoop
task (Runnable或Callable) 可以直接提交到 EventLoop 實現即刻或者延後執行。根據配置和可用的CPU核,可以建立多個 EventLoop 來優化資源利用。
一個 event 的本質決定了它將如何被處理;它可能從網路協議棧傳送資料到你的應用,或者反過來,或者做一些完全不一樣的事情。但是 event 處理邏輯必須足夠通用和靈活,來對付所有可能的情況。
所以,在 Netty 4,所有的 I/O 操作和 event 都是由分配給 EventLoop 的那一個 Thread 來處理的。Netty 4 採用的執行緒模型,在同一個執行緒的 EventLoop 中處理所有發生的事。
7.3 EventLoopGroup
- EventLoopGroup 負責分配 EventLoop 到新建立的 Channel
- 非同步實現只用了很少 EventLoop,這幾個 EventLoop 被所有 Channel 共享
- 一但 Channel 被指派了一個 EventLoop,在它的整個生命週期過程中,都會用這個 EventLoop
為 Channel 的 I/O 和 event 提供服務的 EventLoop 都包含在一個 EventLoopGroup 中。EventLoop 建立和分配的方式根據傳輸實現的不同而有所不同。
非同步實現只用了很少幾個 EventLoop(和它們關聯的執行緒),在目前 Netty 的模型中,這幾個 EventLoop 被所有 Channel 共享。這讓很多 Channel 被最少數量的執行緒服務,而不是每個 Channel 分配一個執行緒。
EventLoopGroup 負責分配一個 EventLoop 到每個新建立的 Channel。在目前的實現中,採用迴圈 (round-robin) 策略可以滿足一個平衡的分配,同一個 Eventloop 還可能會被分配到多個 Channel。
「本節參考」 第七章 EventLoop和執行緒模型