1. 程式人生 > 實用技巧 >Netty Channel

Netty Channel

ChannelHandler是netty中的核心處理部分,我們使用netty的絕大部分程式碼都寫在這部分,所以瞭解它的一些機制和特性是很有必要的

Channel

Channel介面抽象了底層socket的一些狀態屬性以及呼叫方法

image

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

image

Channel生命週期

image

ChannelHandler

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

ChannelHandler生命週期方法

image

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

ChannelInboundHandler

image

介紹一下這些回撥方法被觸發的時機

回撥方法觸發時機clientserver
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
回撥方法觸發時機clientserver
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的頭和尾
這個特性在不需要呼叫整個鏈路的情況下可以使用,可以增加一些效率

上述元件之間的關係

    1. 每個Channel會繫結一個ChannelPipeline,ChannelPipeline中也會持有Channel的引用
    2. ChannelPipeline持有ChannelHandlerContext鏈路,保留ChannelHandlerContext的頭尾節點指標
    3. 每個ChannelHandlerContext會對應一個ChannelHandler,也就相當於ChannelPipeline持有ChannelHandler鏈路
    4. ChannelHandlerContext同時也會持有ChannelPipeline引用,也就相當於持有Channel引用
    5. ChannelHandler鏈路會根據Handler的型別,分為InBound和OutBound兩條鏈路