Netty 框架總結「ChannelHandler 及 EventLoop」

「博客搬家」 原地址: 簡書 原發表時間: 2017-05-05html

學習了一段時間的 Netty,將重點與學習心得總結以下,本文主要總結ChannelHandler 及 EventLoop 的知識點和基本用法,本文章節排序參照《Netty in Action》的章節排序。編程

如下內容主要參考「併發編程網」的 《Netty in Action》中文版 以及《Netty in Action》原版圖書,輔助參考 Essential Netty in Action 《Netty 實戰(精髓)》 以及 Netty 官網的 Netty 4.1 JavaDocapi

6. ChannelHandler 和 ChannelPipeline

一個 Channel 正常的生命週期以下圖所示。隨着狀態發生變化,相應的 event 產生。這些 event 被轉發到 ChannelPipeline 中的 ChannelHandler 來採起相應的操做。promise

Channel狀態模型

6.1 ChannelHandler

ChannelHandler 有兩個重要的子接口:bash

  • 「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類層級關係

  • ChannelHandlerAdapter 提供了工具方法 isSharable()。若是類實現帶 @Sharable 註解,那麼這個方法就會返回 true,意味着這個對象能夠被添加到多個 ChannelPipeline 中。ide

  • 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。

ChannelPipeline 和 ChannelHandlers

「本節參考」 第六章 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

針對非阻塞傳輸的EventLoop分配

爲 Channel 的 I/O 和 event 提供服務的 EventLoop 都包含在一個 EventLoopGroup 中。EventLoop 建立和分配的方式根據傳輸實現的不一樣而有所不一樣。

異步實現只用了不多幾個 EventLoop(和它們關聯的線程),在目前 Netty 的模型中,這幾個 EventLoop 被全部 Channel 共享。這讓不少 Channel 被最少數量的線程服務,而不是每一個 Channel 分配一個線程。

EventLoopGroup 負責分配一個 EventLoop 到每一個新建立的 Channel。在目前的實現中,採用循環 (round-robin) 策略能夠知足一個平衡的分配,同一個 Eventloop 還可能會被分配到多個 Channel。

「本節參考」 第七章 EventLoop和線程模型

參考連接

  1. 《Netty in Action》中文版
  2. Essential Netty in Action 《Netty 實戰(精髓)》
  3. Netty 4.1 JavaDoc
相關文章
相關標籤/搜索