天天看點

netty系列之:channelHandlerContext詳解

目錄

  • 簡介
  • ChannelHandlerContext和它的應用
  • AbstractChannelHandlerContext
  • DefaultChannelHandlerContext
  • 總結

簡介

我們知道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/

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

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