Introduction

We know that ChannelHandler has two very important sub-interfaces, namely ChannelOutboundHandler and ChannelInboundHandler. Basically, these two handler interfaces define the processing logic of all channel inbound and outbound.

Whether it is ChannelHandler or ChannelOutboundHandler and ChannelInboundHandler, almost all of their methods have a ChannelHandlerContext parameter, so what is the purpose of this ChannelHandlerContext? What does it have to do with handler and channel?

ChannelHandlerContext and its application

Friends who are familiar with netty should have been exposed to ChannelHandlerContext. If not, here is a simple handler example:

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();
    }
}

The handler here inherits SimpleChannelInboundHandler and only needs to implement the corresponding method. The channelActive method is implemented here. In the channelActive method, a ChannelHandlerContext parameter is passed in, and we can call some of its methods by using the ChannelHandlerContext.

Let's first look at the definition of ChannelHandlerContext:

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {

First, ChannelHandlerContext is an AttributeMap that can be used to store multiple data.

Then ChannelHandlerContext inherits ChannelInboundInvoker and ChannelOutboundInvoker, which can trigger some methods of inbound and outbound.

In addition to some inherited methods, ChannelHandlerContext can also be used as a communication bridge between channel, handler and pipline, because the corresponding channel, handler and pipline can be obtained from ChannelHandlerContext:

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

Also note that ChannelHandlerContext also returns an EventExecutor, which is used to perform specific tasks:

EventExecutor executor();

Next, let's take a look at the implementation of ChannelHandlerContext.

AbstractChannelHandlerContext

AbstractChannelHandlerContext is a very important implementation of ChannelHandlerContext. Although AbstractChannelHandlerContext is an abstract class, it basically implements all the functions of ChannelHandlerContext.

First look at the definition of AbstractChannelHandlerContext:

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint

AbstractChannelHandlerContext is a concrete implementation of ChannelHandlerContext.

Generally speaking, one handler corresponds to one ChannelHandlerContext, but there may be more than one handler in a program, so how to get other handlers in one handler?

In AbstractChannelHandlerContext, there are two next and prev which are also of type AbstractChannelHandlerContext, so that multiple AbstractChannelHandlerContext can build a doubly linked list. Thus, in a ChannelHandlerContext, other ChannelHandlerContexts can be obtained to obtain the handler processing chain.

    volatile AbstractChannelHandlerContext next;
    volatile AbstractChannelHandlerContext prev;

The pipeline and executor in AbstractChannelHandlerContext are passed in through the constructor:

    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;
    }

Some friends may have questions, how are the channels and handlers in ChannelHandlerContext obtained?

For the channel, it is obtained through the pipeline:

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

For handler, it is not implemented in AbstractChannelHandlerContext, it needs to be implemented in the class that inherits AbstractChannelHandlerContext.

For EventExecutor, a new EventExecutor can be passed in to AbstractChannelHandlerContext through the constructor. If it is not passed in or is empty, the EventLoop that comes with the channel will be used:

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

Because EventLoop inherits from OrderedEventExecutor, it is also an EventExecutor.

EventExecutor is mainly used to submit tasks asynchronously for execution. In fact, almost all methods from ChannelInboundInvoker and ChannelOutboundInvoker in ChannelHandlerContext are executed through EventExecutor.

For ChannelInboundInvoker, we take the method fireChannelRegistered as an example:

    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 calls the invokeChannelRegistered method, invokeChannelRegistered calls the execute method of EventExecutor, and encapsulates the real calling logic in a runnable class for execution.

Note that there is a judgment of whether the executor is in the eventLoop before calling the executor.execute method. If the executor is already in the eventLoop, the task can be executed directly without starting a new thread.

For ChannelOutboundInvoker, let's take the bind method as an example to see how EventExecutor is used:

    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;
    }

It can be seen that the execution logic is very similar to the invokeChannelRegistered method. It is also first to determine whether the executor is in the eventLoop, if it is, it will be executed directly, and if it is not, it will be executed in the executor.

In the above two examples, the corresponding methods of next are called, respectively next.invokeChannelRegistered and next.invokeBind.

We know that ChannelHandlerContext is just an encapsulation, and it does not have much business logic itself, so the corresponding method called by next is actually the business logic in the ChannelInboundHandler and ChannelOutboundHandler encapsulated in the Context, as shown below:

    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);
        }
    }

Therefore, from AbstractChannelHandlerContext, we can know that the methods defined in the ChannelHandlerContext interface are the specific implementations in the called handler, and the Context is only the encapsulation of the handler.

DefaultChannelHandlerContext

DefaultChannelHandlerContext is a concrete implementation of AbstractChannelHandlerContext.

When we explained AbstractChannelHandlerContext, we mentioned that there is no specific handler implementation defined in AbstractChannelHandlerContext, and this implementation is carried out in DefaultChannelHandlerContext.

DefaultChannelHandlerContext is very simple, let's take a look at its specific implementation:

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;
    }
}

An additional ChannelHandler property is provided in DefaultChannelHandlerContext to store the incoming ChannelHandler.

At this point, DefaultChannelHandlerContext can pass in all necessary handlers, channels, pipelines and EventExecutors in ChannelHandlerContext.

Summarize

In this section, we introduced ChannelHandlerContext and its several basic implementations. We learned that ChannelHandlerContext is the encapsulation of handler, channel and pipline. The business logic in ChannelHandlerContext actually calls the corresponding method of the underlying handler. This is also the method we need to implement in the custom handler.

This article has been included in http://www.flydean.com/04-4-netty-channelhandlercontext/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com