netty系列之:channelHandlerContext詳解

flydean發表於2022-03-02

簡介

我們知道ChannelHandler有兩個非常重要的子介面,分別是ChannelOutboundHandler和ChannelInboundHandler,基本上這兩個handler介面定義了所有channel inbound和outbound的處理邏輯。

不管是ChannelHandler還是ChannelOutboundHandler和ChannelInboundHandler,幾乎他們中所有的方法都帶有一個ChannelHandlerContext引數,那麼這個ChannelHandlerContext到底是做什麼用的呢?它和handler、channel有什麼關係呢?

ChannelHandlerContext和它的應用

熟悉netty的朋友應該都接觸過ChannelHandlerContext,如果沒有的話,這裡有一個簡單的handler的例子:

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("accepted channel: {}", ctx.channel());
        log.info("accepted channel parent: {}", ctx.channel().parent());
        // channel活躍
        ctx.write("Channel Active狀態!\r\n");
        ctx.flush();
    }
}

這裡的handler繼承了SimpleChannelInboundHandler,只需要實現對應的方法即可。這裡實現的是channelActive方法,在channelActive方法中,傳入了一個ChannelHandlerContext引數,我們可以通過使用ChannelHandlerContext來呼叫它的一些方法。

先來看一下ChannelHandlerContext的定義:

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {

首先ChannelHandlerContext是一個AttributeMap,可以用來儲存多個資料。

然後ChannelHandlerContext繼承了ChannelInboundInvoker和ChannelOutboundInvoker,可以觸發inbound和outbound的一些方法。

除了繼承來的一些方法之外,ChannelHandlerContext還可以作為channel,handler和pipline的溝通橋樑,因為可以從ChannelHandlerContext中獲取到對應的channel,handler和pipline:

Channel channel();
ChannelHandler handler();
ChannelPipeline pipeline();

還要注意的是ChannelHandlerContext還返回一個EventExecutor,用來執行特定的任務:

EventExecutor executor();

接下來,我們具體看一下ChannelHandlerContext的實現。

AbstractChannelHandlerContext

AbstractChannelHandlerContext是ChannelHandlerContext的一個非常重要的實現,雖然AbstractChannelHandlerContext是一個抽象類,但是它基本上實現了ChannelHandlerContext的所有功能。

首先看一下AbstractChannelHandlerContext的定義:

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint

AbstractChannelHandlerContext是ChannelHandlerContext的一個具體實現。

通常來說一個handler對應一個ChannelHandlerContext,但是在一個程式中可能會有多於一個handler,那麼如何在一個handler中獲取其他的handler呢?

在AbstractChannelHandlerContext中有兩個同樣是AbstractChannelHandlerContext型別的next和prev,從而使得多個AbstractChannelHandlerContext可以構建一個雙向連結串列。從而可以在一個ChannelHandlerContext中,獲取其他的ChannelHandlerContext,從而獲得handler處理鏈。

    volatile AbstractChannelHandlerContext next;
    volatile AbstractChannelHandlerContext prev;

AbstractChannelHandlerContext中的pipeline和executor都是通過建構函式傳入的:

    AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                                  String name, Class<? extends ChannelHandler> handlerClass) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        this.executionMask = mask(handlerClass);
        // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
        ordered = executor == null || executor instanceof OrderedEventExecutor;
    }

可能有朋友會有疑問了,ChannelHandlerContext中的channel和handler是如何得到的呢?

對於channel來說,是通過pipeline來獲取的:

public Channel channel() {
        return pipeline.channel();
    }

對於handler來說,在AbstractChannelHandlerContext中並沒有對其進行實現,需要在繼承AbstractChannelHandlerContext的類中進行實現。

對於EventExecutor來說,可以通過建構函式向AbstractChannelHandlerContext傳入一個新的EventExecutor,如果沒有傳入或者傳入為空的話,則會使用channel中自帶的EventLoop:

    public EventExecutor executor() {
        if (executor == null) {
            return channel().eventLoop();
        } else {
            return executor;
        }
    }

因為EventLoop繼承自OrderedEventExecutor,所以它也是一個EventExecutor。

EventExecutor主要用來非同步提交任務來執行,事實上ChannelHandlerContext中幾乎所有來自於ChannelInboundInvoker和ChannelOutboundInvoker的方法都是通過EventExecutor來執行的。

對於ChannelInboundInvoker來說,我們以方法fireChannelRegistered為例:

    public ChannelHandlerContext fireChannelRegistered() {
        invokeChannelRegistered(findContextInbound(MASK_CHANNEL_REGISTERED));
        return this;
    }

    static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

fireChannelRegistered呼叫了invokeChannelRegistered方法,invokeChannelRegistered則呼叫EventExecutor的execute方法,將真實的呼叫邏輯封裝在一個runnable類中執行。

注意,在呼叫executor.execute方法之前有一個executor是否在eventLoop中的判斷。如果executor已經在eventLoop中了,那麼直接執行任務即可,不需要啟用新的執行緒。

對於ChannelOutboundInvoker來說,我們以bind方法為例,看一下EventExecutor是怎麼使用的:

    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        ObjectUtil.checkNotNull(localAddress, "localAddress");
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }

        final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null, false);
        }
        return promise;
    }

可以看到執行的邏輯和invokeChannelRegistered方法很類似,也是先判斷executor在不在eventLoop中,如果在的話直接執行,如果不在則放在executor中執行。

上面的兩個例子中都呼叫了next的相應方法,分別是next.invokeChannelRegistered和next.invokeBind。

我們知道ChannelHandlerContext只是一個封裝,它本身並沒有太多的業務邏輯,所以next呼叫的相應方法,實際上是Context中封裝的ChannelInboundHandler和ChannelOutboundHandler中的業務邏輯,如下所示:

    private void invokeUserEventTriggered(Object event) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).userEventTriggered(this, event);
            } catch (Throwable t) {
                invokeExceptionCaught(t);
            }
        } else {
            fireUserEventTriggered(event);
        }
    }
    private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            bind(localAddress, promise);
        }
    }

所以,從AbstractChannelHandlerContext可以得知,ChannelHandlerContext介面中定義的方法都是呼叫的handler中具體的實現,Context只是對handler的封裝。

DefaultChannelHandlerContext

DefaultChannelHandlerContext是AbstractChannelHandlerContext的一個具體實現。

我們在講解AbstractChannelHandlerContext的時候提到過,AbstractChannelHandlerContext中並沒有定義具體的handler的實現,而這個實現是在DefaultChannelHandlerContext中進行的。

DefaultChannelHandlerContext很簡單,我們看一下它的具體實現:

final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {

    private final ChannelHandler handler;

    DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, handler.getClass());
        this.handler = handler;
    }

    @Override
    public ChannelHandler handler() {
        return handler;
    }
}

DefaultChannelHandlerContext中額外提供了一個ChannelHandler屬性,用來儲存傳入的ChannelHandler。

到此DefaultChannelHandlerContext可以傳入ChannelHandlerContext中一切必須的handler,channel,pipeline和EventExecutor。

總結

本節我們介紹了ChannelHandlerContext和它的幾個基本實現,瞭解到了ChannelHandlerContext是對handler,channel和pipline的封裝,ChannelHandlerContext中的業務邏輯,實際上是呼叫的是底層的handler的對應方法。這也是我們在自定義handler中需要實現的方法。

本文已收錄於 http://www.flydean.com/04-4-netty-channelhandlercontext/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章