Netty學習之ChannelHandler&ChannelPipeline

weixin_33860722發表於2018-10-10

Netty學習之ChannelHandler&ChannelPipeline

前言

ChannelHandler

Channel生命週期

  • ChannelUnregistered,Channel被建立,但是還沒有註冊到EventLoop中
  • ChannelRegisted,Channel註冊到EventLoop中
  • ChannelActive,Channel啟用(連線到遠端埠),能夠傳送以及接受訊息
  • ChannelInActive,Channel沒有連線到遠端埠

生命週期流程

ChannelRegisted --> ChannelActive --> ChannelInactive --> ChannelUnregistered

ChannelHandler生命週期

  • handlerAdded,當ChannelHandler新增到ChannelPipeline時呼叫
  • handlerRemoved,當ChannelHandler從ChannelPipline中移除時呼叫
  • exceptionCaught,在ChannelPipline處理時發生異常時呼叫

注意上面每個方法都接收一個ChannelHandlerContext作為引數

ChannelHandler有重要的兩個子介面

  • ChannelInboundHandler,處理輸入的資料並且處理各種狀態轉變
  • ChannelOutboundHandler,處理輸出的資料並且攔截各種操作

ChannelInboundHandler生命週期

  • channelRegistered,當Channel註冊到對應的EventLoop並且能夠處理I/O時呼叫
  • channelUnregistered,當channel從EventLoop中移除並且不能處理任何I/O時呼叫
  • channelActive,當channel啟用時呼叫,此時channel已經連線並且準備好
  • channelInactive,當通道斷開連線時呼叫
  • channelReadComplete,讀操作在Channel中已經完成時呼叫(讀取完成)
  • channelRead,資料從channel中讀取時呼叫(開始讀取)
  • channelWritabilityChanged,寫狀態改變時呼叫
  • userEventTriggered,當因為管線中傳遞物件時ChannelInboundHanlder.fireUserEventTriggered()被呼叫時呼叫

當覆蓋ChannelInboundHandler的channelRead方法時,需要手動釋放對應的記憶體,可以通過ReferenceCountUtil.release(msg)方法進行釋放操作

更簡單的方式是通過SimpleChannelInboundHandler,然後覆蓋其channelRead0()方法,該方法會自動釋放資源,所以,不能儲存稍後會使用到的資料,因為這些資料都會失效,通過原始碼就可以發現,該方法被channelRead()呼叫,並且channelRead0()方法被呼叫後,會釋放所佔用記憶體。

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    boolean release = true;
    try {
        if (acceptInboundMessage(msg)) {
            @SuppressWarnings("unchecked")
            I imsg = (I) msg;
            // 這裡呼叫channelRead0
            channelRead0(ctx, imsg);
        } else {
            release = false;
            ctx.fireChannelRead(msg);
        }
    } finally {
        if (autoRelease && release) {
            // 釋放空間
            ReferenceCountUtil.release(msg);
        }
    }
}

ChannelOutboundHandler

ChannelOutboundHandler的方法由Channel、ChannelPipeline、ChannelHandlerContext呼叫

主要的操作方法有write()flush()

大部分的方法都會帶一個ChannelPromise引數,當操作完成時可以收到通知,該介面是ChannelFuture的子介面。

在Netty中,同時還提供了ChannelInboundHandlerAdapterChannelOutboundHanlderAdapter類,一般只需要繼承這兩個類,然後覆蓋想要實現邏輯的方法即可。

資源管理

在使用ChannelInboundHanlder#channelRead()或者ChannelOutboundHandler#write()時,需要保證沒有資源洩露,Netty使用引用技術來處理快取的ByteBuf,所以使用完ByteBuf之後更改引用是非常重要的。

Netty提供了一個ResouceLeakDetecor工具類用於檢測是否發生記憶體洩露,如果發生洩露,則會列印對應的日誌資訊。有四個不同的級別

  • DISABLED,關閉洩露檢查
  • SIMPLE,預設級別,樣本率為1%
  • ADVANCED,報告記憶體洩露以及對應的資訊
  • PARANOID,每個類均取樣,會嚴重影響效能

如果一個訊息已經被消費或者被丟棄,並且不再在管道中傳遞給下一個接受者,則需要手動呼叫ReferenceCountUtil.release(msg)將其釋放,如果訊息到達傳輸層,當訊息被寫出或者通道關閉後,會自動進行釋放。

ChannelPipeline

每個新的Channel都會繫結一個新的ChannelPipeline,並且是永久性的,Channel不能繫結新的ChannelPipeline,也不能從當前Channel中解綁(開發者無需關心繫結以及解綁)

ChannelHandlerContext使得一個ChannelHanlder能夠與ChannelPipeline以及其他的ChannelHandler進行通訊

由於ChannelPipeline傳播事件,它會檢測在管線中的下一個ChannelHandler是否匹配移動的方法,如果不匹配,則跳過該ChannleHandler,並且處理下一個,直到找到一個匹配的ChannelHandler。

ChannelPipeline中提供了眾多的方法用於操作ChannelHandler,如addFirstaddLastremove等。同時,還提供了眾多的方法用於呼叫下一個ChannelInboundHandler方法的方法,如fireChannelRead()fireExceptionCaught()等,呼叫下一個ChannelOutboundHandler方法的方法,如flush()write()writeAndFlush()等。

ChannelHandlerContext

ChannelHandlerContext表示ChannelHandler和ChannelPipeline之間的連線,當一個ChannelHandler被新增到ChannelPipeline時會被建立。

ChannelPipeline的主要作用是管理其所關聯的ChannelHandler與其他在ChannelPipeline中的ChannelHandler的互動

如果呼叫Channel或者ChannelPipeline中的某些方法,對應的操作會在整個ChannelPipeline中傳輸,而通過ChannelHandlerContext呼叫,只傳輸給下一個能夠處理該事件的ChannelHandler

異常處理

如果是ChannelInboundHandler中出現異常,會逐步進行傳遞,所以,一般在最後一個channelHandler中覆蓋caughtException(),實現對應的異常處理邏輯

如果是在ChannelOutboundHandler中出現異常,可以有一下機制

  • 每一個outbound操作會返回一個ChannelFuture,註冊了的ChannelFutureListener會收到對應的訊息,即成功或者失敗
  • 幾乎所有的channelOutboundHanlder會傳遞一個ChannelPromise例項,該例項是ChannelFuture的子類,ChannelPromise能夠註冊監聽器用於非同步通知,也可以直接通知操作,setSuccess()setFailure()

總結

本小節我們主要詳細學習了ChannelHandler以及ChannelPipeline,ChannelHandler是Netty應用中邏輯的存放地方,基本上我們主要互動的物件也是ChannelHandler,不同的ChannelHandler之間通過ChannelPipeline連線起來,構成一個Handler處理鏈,而ChannelHandler與ChannelPipeline之間則是通過ChannelHandlerContext進行連線,不同的物件用途不同,使用的時候需要注意。

相關文章