Netty Channel
ChannelHandler是netty中的核心處理部分,我們使用netty的絕大部分程式碼都寫在這部分,所以瞭解它的一些機制和特性是很有必要的
Channel
Channel介面抽象了底層socket的一些狀態屬性以及呼叫方法
image針對不同型別的socket提供不同的子類實現。
imageChannel生命週期
imageChannelHandler
ChannelHandler用於處理Channel對應的事件
ChannelHandler接口裡面只定義了三個生命週期方法,我們主要實現它的子介面ChannelInboundHandler和ChannelOutboundHandler,為了便利,框架提供了ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter和ChannelDuplexHandler這三個適配類,在使用的時候只需要實現你關注的方法即可
ChannelHandler生命週期方法
imageChannelHandler裡面定義三個生命週期方法,分別會在當前ChannelHander加入ChannelHandlerContext中,從ChannelHandlerContext中移除,以及ChannelHandler回撥方法出現異常時被回撥
ChannelInboundHandler
image介紹一下這些回撥方法被觸發的時機
回撥方法 | 觸發時機 | 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
image.png回撥方法 | 觸發時機 | 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做的工作相當重要
image上面的整條鏈式的呼叫是通過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兩條鏈路