ChannelHandler是netty中的核心處理部分,我們使用netty的絕大部分代碼都寫在這部分,所以了解它的一些機制和特性是很有必要的
Channel
Channel接口抽象了底層socket的一些狀態屬性以及調用方法

針對不同類型的socket提供不同的子類實現。

Channel生命周期

ChannelHandler
ChannelHandler用於處理Channel對應的事件
ChannelHandler接口里面只定義了三個生命周期方法,我們主要實現它的子接口ChannelInboundHandler和ChannelOutboundHandler,為了便利,框架提供了ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter和ChannelDuplexHandler這三個適配類,在使用的時候只需要實現你關注的方法即可
ChannelHandler生命周期方法

ChannelHandler里面定義三個生命周期方法,分別會在當前ChannelHander加入ChannelHandlerContext中,從ChannelHandlerContext中移除,以及ChannelHandler回調方法出現異常時被回調
ChannelInboundHandler

介紹一下這些回調方法被觸發的時機
回調方法 | 觸發時機 | client | server |
---|---|---|---|
channelRegistered | 當前channel注冊到EventLoop | true | true |
channelUnregistered | 當前channel從EventLoop取消注冊 | true | true |
channelActive | 當前channel激活的時候 | true | true |
channelInactive | 當前channel不活躍的時候,也就是當前channel到了它生命周期末 | true | true |
channelRead | 當前channel從遠端讀取到數據 | true | true |
channelReadComplete | channel read消費完讀取的數據的時候被觸發 | true | true |
userEventTriggered | 用戶事件觸發的時候 | ||
channelWritabilityChanged | channel的寫狀態變化的時候觸發 |
可以注意到每個方法都帶了ChannelHandlerContext作為參數,具體作用是,在每個回調事件里面,處理完成之后,使用ChannelHandlerContext的fireChannelXXX方法來傳遞給下個ChannelHandler,netty的codec模塊和業務處理代碼分離就用到了這個鏈路處理
ChannelOutboundHandler

回調方法 | 觸發時機 | client | server |
---|---|---|---|
bind | bind操作執行前觸發 | false | true |
connect | connect 操作執行前觸發 | true | false |
disconnect | disconnect 操作執行前觸發 | true | false |
close | close操作執行前觸發 | false | true |
deregister | deregister操作執行前觸發 | ||
read | read操作執行前觸發 | true | true |
write | write操作執行前觸發 | true | true |
flush | flush操作執行前觸發 | true | true |
注意到一些回調方法有ChannelPromise這個參數,我們可以調用它的addListener注冊監聽,當回調方法所對應的操作完成后,會觸發這個監聽
下面這個代碼,會在寫操作完成后觸發,完成操作包括成功和失敗
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { ctx.write(msg,promise); System.out.println("out write"); promise.addListener(new GenericFutureListener<Future<? super Void>>() { @Override public void operationComplete(Future<? super Void> future) throws Exception { if(future.isSuccess()){ System.out.println("OK"); } } }); }
ChannelInboundHandler和ChannelOutboundHandler的區別
個人感覺in和out的區別主要在於ChannelInboundHandler的channelRead和channelReadComplete回調和ChannelOutboundHandler的write和flush回調上,ChannelOutboundHandler的channelRead回調負責執行入棧數據的decode邏輯,ChannelOutboundHandler的write負責執行出站數據的encode工作。其他回調方法和具體觸發邏輯有關,和in與out無關。
ChannelHandlerContext
每個ChannelHandler通過add方法加入到ChannelPipeline中去的時候,會創建一個對應的ChannelHandlerContext,並且綁定,ChannelPipeline實際維護的是ChannelHandlerContext 的關系
在DefaultChannelPipeline源碼中可以看到會保存第一個ChannelHandlerContext以及最后一個ChannelHandlerContext的引用
final AbstractChannelHandlerContext head; final AbstractChannelHandlerContext tail;
而在AbstractChannelHandlerContext源碼中可以看到
volatile AbstractChannelHandlerContext next; volatile AbstractChannelHandlerContext prev;
每個ChannelHandlerContext之間形成雙向鏈表
ChannelPipeline
在Channel創建的時候,會同時創建ChannelPipeline
protected AbstractChannel(Channel parent) { this.parent = parent; id = newId(); unsafe = newUnsafe(); pipeline = newChannelPipeline(); }
在ChannelPipeline中也會持有Channel的引用
protected DefaultChannelPipeline newChannelPipeline() { return new DefaultChannelPipeline(this); }
ChannelPipeline會維護一個ChannelHandlerContext的雙向鏈表
final AbstractChannelHandlerContext head; final AbstractChannelHandlerContext tail;
鏈表的頭尾有默認實現
protected DefaultChannelPipeline(Channel channel) { this.channel = ObjectUtil.checkNotNull(channel, "channel"); succeededFuture = new SucceededChannelFuture(channel, null); voidPromise = new VoidChannelPromise(channel, true); tail = new TailContext(this); head = new HeadContext(this); head.next = tail; tail.prev = head; }
我們添加的自定義ChannelHandler會插入到head和tail之間,如果是ChannelInboundHandler的回調,根據插入的順序從左向右進行鏈式調用,ChannelOutboundHandler則相反
具體關系如下,但是下圖沒有把默認的head和tail畫出來,這兩個ChannelHandler做的工作相當重要

上面的整條鏈式的調用是通過Channel接口的方法直接觸發的,如果使用ChannelContextHandler的接口方法間接觸發,鏈路會從ChannelContextHandler對應的ChannelHandler開始,而不是從頭或尾開始
HeadContext
HeadContext實現了ChannelOutboundHandler,ChannelInboundHandler這兩個接口
class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler
因為在頭部,所以說HeadContext中關於in和out的回調方法都會觸發
關於ChannelInboundHandler,HeadContext的作用是進行一些前置操作,以及把事件傳遞到下一個ChannelHandlerContext的ChannelInboundHandler中去
看下其中channelRegistered的實現
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { invokeHandlerAddedIfNeeded(); ctx.fireChannelRegistered(); }
從語義上可以看出來在把這個事件傳遞給下一個ChannelHandler之前會回調ChannelHandler的handlerAdded方法
而有關ChannelOutboundHandler接口的實現,會在鏈路的最后執行,看下write方法的實現
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { unsafe.write(msg, promise); }
這邊的unsafe接口封裝了底層Channel的調用,之所以取名為unsafe,是不需要用戶手動去調用這些方法。這個和阻塞原語的unsafe不是同一個
也就是說,當我們通過Channel接口執行write之后,會執行ChannelOutboundHandler鏈式調用,在鏈尾的HeadContext ,在通過unsafe回到對應Channel做相關調用
從netty Channel接口的實現就能論證這個
public ChannelFuture write(Object msg) { return pipeline.write(msg); }
TailContext
TailContext實現了ChannelInboundHandler接口,會在ChannelInboundHandler調用鏈最后執行,只要是對調用鏈完成處理的情況進行處理,看下channelRead實現
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { onUnhandledInboundMessage(msg); }
如果我們自定義的最后一個ChannelInboundHandler,也把處理操作交給下一個ChannelHandler,那么就會到TailContext,在TailContext會提供一些默認處理
protected void onUnhandledInboundMessage(Object msg) { try { logger.debug( "Discarded inbound message {} that reached at the tail of the pipeline. " + "Please check your pipeline configuration.", msg); } finally { ReferenceCountUtil.release(msg); } }
比如channelRead中的onUnhandledInboundMessage方法,會把msg資源回收,防止內存泄露
強調一點的是,如果要執行整個鏈路,必須通過調用Channel方法觸發,ChannelHandlerContext引用了ChannelPipeline,所以也能間接操作channel的方法,但是會從當前ChannelHandlerContext綁定的ChannelHandler作為起點開始,而不是ChannelHandlerContext的頭和尾
這個特性在不需要調用整個鏈路的情況下可以使用,可以增加一些效率
上述組件之間的關系

- 每個Channel會綁定一個ChannelPipeline,ChannelPipeline中也會持有Channel的引用
- ChannelPipeline持有ChannelHandlerContext鏈路,保留ChannelHandlerContext的頭尾節點指針
- 每個ChannelHandlerContext會對應一個ChannelHandler,也就相當於ChannelPipeline持有ChannelHandler鏈路
- ChannelHandlerContext同時也會持有ChannelPipeline引用,也就相當於持有Channel引用
- ChannelHandler鏈路會根據Handler的類型,分為InBound和OutBound兩條鏈路