写点什么

netty 系列之:channelHandlerContext 详解

作者:程序那些事
  • 2022 年 2 月 18 日
  • 本文字数:4434 字

    阅读完需:约 15 分钟

netty系列之:channelHandlerContext详解

简介

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

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 刚刚阅读数: 2
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:channelHandlerContext详解