简介

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

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

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

netty系列之:channelHandlerContext详解的更多相关文章

  1. netty系列之:channelPipeline详解

    目录 简介 ChannelPipeline 事件传递 DefaultChannelPipeline 总结 简介 我们在介绍channel的时候提到过,几乎channel中所有的实现都是通过channe ...

  2. nginx高性能WEB服务器系列之四配置文件详解

    nginx系列友情链接:nginx高性能WEB服务器系列之一简介及安装https://www.cnblogs.com/maxtgood/p/9597596.htmlnginx高性能WEB服务器系列之二 ...

  3. mongo 3.4分片集群系列之六:详解配置数据库

    这个系列大致想跟大家分享以下篇章: 1.mongo 3.4分片集群系列之一:浅谈分片集群 2.mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3.mongo 3.4分片集群系列之三:搭建 ...

  4. mongo 3.4分片集群系列之五:详解平衡器

    这个系列大致想跟大家分享以下篇章: 1.mongo 3.4分片集群系列之一:浅谈分片集群 2.mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3.mongo 3.4分片集群系列之三:搭建 ...

  5. Hexo系列(二) 配置文件详解

    Hexo 是一款优秀的博客框架,在使用 Hexo 搭建一个属于自己的博客网站后,我们还需要对其进行配置,使得 Hexo 更能满足自己的需求 这里所说的配置文件,是位于站点根目录下的 _config.y ...

  6. css3系列之transform详解translate

    translate translate这个参数的,是transform 身上的,那么它有什么用呢? 其实他的作用很简单,就是平移,参考自己的位置来平移 translate() translateX() ...

  7. ThreeJS系列1_CinematicCameraJS插件详解

    ThreeJS系列1_CinematicCameraJS插件详解 接着上篇 ThreeJS系列1_CinematicCameraJS插件介绍 看属性的来龙去脉 看方法作用 通过调整属性查看效果 总结 ...

  8. netty系列之:netty中的Channel详解

    目录 简介 Channel详解 异步IO和ChannelFuture Channel的层级结构 释放资源 事件处理 总结 简介 Channel是连接ByteBuf和Event的桥梁,netty中的Ch ...

  9. netty系列之:netty中的ByteBuf详解

    目录 简介 ByteBuf详解 创建一个Buff 随机访问Buff 序列读写 搜索 其他衍生buffer方法 和现有JDK类型的转换 总结 简介 netty中用于进行信息承载和交流的类叫做ByteBu ...

随机推荐

  1. 网络流 HLPP 板子

    #include<bits/stdc++.h> using namespace std; const int MM=4e5+5,inf=0x3f3f3f3f; int n,m,s,t,to ...

  2. 如何在Xamarin中快速集成Android版认证服务-手机号码篇

    Xamarin作为微软提供的移动服务多系统开发平台,成为很多开发者首选的应用开发平台.AppGallery Connect(以下简称AGC)也在逐步的支持Xamarin的SDK.认证服务也是支持Xam ...

  3. golang中值类型的嵌入式字段和指针类型的嵌入式字段

    总结: 1. 值类型的嵌入式字段,该类型拥有值类型的方法集,没有值指针类型的方法集 2. 指针类型的嵌入式字段,该类型拥有值指针类型的方法集,没有值类型的方法集,并且,该类型的指针类型也有值指针类型的 ...

  4. golang中匿名函数的应用-回调函数-闭包

    package main import ( "fmt" "strconv" ) type funcType func(int, int) int // 自定义函 ...

  5. 使用Hot Chocolate和.NET 6构建GraphQL应用(3) —— 实现Query基础功能

    系列导航 使用Hot Chocolate和.NET 6构建GraphQL应用文章索引 需求 在本文中,我们通过一个简单的例子来看一下如何实现一个最简单的GraphQL的接口. 实现 引入Hot Cho ...

  6. UVA1619 感觉不错 Feel Good(良好的感觉) 题解

    0.题面: 给出正整数n和一个(1 <= n <= 100 000)长度的数列,要求找出一个子区间,使这个子区间的数字和乘上子区间中的最小值最大.输出这个最大值与区间的两个端点. 1.思路 ...

  7. Hbase 项目

     需求分析 1) 微博内容的浏览,数据库表设计 2) 用户社交体现:关注用户,取关用户 3) 拉取关注的人的微博内容 表结构 代码实现 1) 创建命名空间以及表名的定义 2) 创建微博内容表 3) 创 ...

  8. RocketMq如何顺序消费的消息offest

    RocketMq对于消费者对于消息的如何处理的 1:如果设置了自动提交 返回: ConsumeOrderlyStatus.COMMIT, ConsumeOrderlyStatus.ROLLBACK, ...

  9. 使用XmlWriter写入XML

    麻了..整理完了发现XmlWriter不能添加元素,只能重写,还是得用Xdocument..好像DOM能实现添加元素 点击查看代码 **MemoryStream msXml = new MemoryS ...

  10. 布客·ApacheCN 编程/后端/大数据/人工智能学习资源 2020.11

    公告 我们始终与所有创作者站在一起,为创作自由而战.我们还会提供一切必要的技术支持. 我们全力支持科研开源(DOCX)计划.希望大家了解这个倡议,把这个倡议与自己的兴趣点结合,做点力所能及的事情. 我 ...