1 概述
ChannelHandlerContext 代表 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline,都会创建 ChannelHandlerContext。
1.1 主要功能
管理它所关联的 ChannelHandler 和在同一个 ChannelPipeline 中的其他 ChannelHandler 之间的交互。
ChannelHandlerContext一些方法也存在于 Channel 和 ChannelPipeline 本身,若:
- 调用 Channel 或 ChannelPipeline 的这些方法,它们将沿整个 ChannelPipeline 传播
- 调用 ChannelHandlerContext 的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该ChannelPipeline 中的下一个能够处理该事件的 ChannelHandler
1.2 ChannelHandlerContext API
表 6-10 ChannelHandlerContext 的 API:
注意到最后一个 read 方法的注释①:通过配合 ChannelConfig.setAutoRead(boolean autoRead)方法,可以实现反应式系统的特性之一背压(back-pressure)。
FAQ
- ChannelHandlerContext 和 ChannelHandler 之间的关联(绑定)永远不会改变,所以对它的引用安全
- 相较其他类的同名方法,ChannelHandler Context的方法将产生更短的事件流,尽可能利用该特性获得最大性能
2 使用 ChannelHandlerContext
2.1 家族关系
图 6-4 展示家族关系:
2.2 实例
通过 ChannelHandlerContext 获取到 Channel 的引用。调用Channel#write()将导致写入事件从tail到head地流经 ChannelPipeline:
代码清单 6-6:从 ChannelHandlerContext 访问 Channel
ChannelHandlerContext ctx = ..;
// 获取到与 ChannelHandlerContext相关联的 Channel 的引用
Channel channel = ctx.channel();
// 通过 Channel 写入缓冲区
channel.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
类似例子,但这次是写入 ChannelPipeline。ChannelPipline引用是通过 ChannelHandlerContext 获取:
代码清单 6-7 通过 ChannelHandlerContext 访问 ChannelPipeline
ChannelHandlerContext ctx = ..;
// 获取到与 ChannelHandlerContext 相关联的 ChannelPipeline 的引用
ChannelPipeline pipeline = ctx.pipeline();
// 通过 ChannelPipeline写入缓冲区
pipeline.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
如图 6-5所见,代码清单 6-6 和代码清单 6-7 中的事件流一样。
被调用的 Channel 或 ChannelPipeline 上的write()方法虽然将一直传播事件通过整个 ChannelPipeline,但在 ChannelHandler 级别上,事件从一个 ChannelHandler 到下一个 ChannelHandler 的移动是由 ChannelHandlerContext 上的调用完成的。
2.3 从 ChannelPipeline 中的某特定点开始传播事件
- 减少将事件传经对它不感兴趣的 ChannelHandler 所带来的开销
- 避免将事件传经那些可能会对它感兴趣的 ChannelHandler
要想调用从某个特定的 ChannelHandler 开始的处理过程,须获取到在(ChannelPipeline)该 ChannelHandler 之前的 ChannelHandler 所关联的 ChannelHandlerContext。这个 ChannelHandlerContext 将调用和它所关联的 ChannelHandler 之后的ChannelHandler。代码清单 6-8 和图 6-6 说明这种用法。
// 获取到 ChannelHandlerContext 的引用
ChannelHandlerContext ctx = ..;
// write()方法将把缓冲区数据发送到下一个 ChannelHandler
ctx.write(Unpooled.copiedBuffer("JavaEdge", CharsetUtil.UTF_8));
如图 6-6,消息将从下一个 ChannelHandler 开始流经 ChannelPipeline,绕过了所有前面的 ChannelHandler:
我们刚才所描述的用例是常见的,对于调用特定的 ChannelHandler 实例上的操作尤其有用。
3 ChannelHandler 和 ChannelHandlerContext 高级用法
3.1 获得private的 ChannelPipeline 的引用
代码清单 6-6,可调用 ChannelHandlerContext#pipeline()获得private修饰的 ChannelPipeline 的引用。使得运行时得以操作 ChannelPipeline 的 ChannelHandler,利用这点实现一些复杂设计。如通过将 ChannelHandler 添加到 ChannelPipeline 实现动态的协议切换。
3.2 缓存到 ChannelHandlerContext 的引用
供稍后使用。可能发生在任何的 ChannelHandler 方法之外,甚至来自不同线程。代码清单 6-9 展示了用这种模式来触发事件。
public class WriteHandler extends ChannelHandlerAdapter {
private ChannelHandlerContext ctx;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
// 存储到 ChannelHandlerContext的引用以供稍后使用
this.ctx = ctx;
}
// 使用之前存储的到 ChannelHandlerContext 的引用来发送消息
public void send(String msg) {
ctx.writeAndFlush(msg);
}
}
3.3 多个 ChannelPipeline 共享同一 ChannelHandler
一个 ChannelHandler 可以从属于多个 ChannelPipeline,所以它也可以绑定到多个 ChannelHandlerContext 实例。这种用法指在多个 ChannelPipeline 共享同一 ChannelHandler,而对应的ChannelHandler 须用@Sharable 注解;否则,试图将它添加到多个 ChannelPipeline 时将触发异常。为安全被用于多个并发的 Channel(即连接),这样的 ChannelHandler 必须是线程安全的。
正例
代码清单 6-10 展示这种模式的一个正确实现:
代码清单 6-10 可共享的 ChannelHandler
// 使用注解@Sharable标注
@Sharable
public class SharableHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("Channel read message: " + msg);
// 记录方法调用,并转发给下一个 ChannelHandler
ctx.fireChannelRead(msg);
}
}
前面的 ChannelHandler 实现符合所有的将其加入到多个 ChannelPipeline 的需求,即使用注解@Sharable,且也不持有任何状态。
反例
相反,代码清单 6-11 中的实现有问题。
代码清单 6-11 @Sharable 的错误用法
// 使用注解@Sharable
@Sharable
public class UnsharableHandler extends ChannelInboundHandlerAdapter {
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 将 count 字段的值加 1
count++;
// 记录方法调用
System.out.println("channelRead(...) called the "
+ count + " time");
// 并转发给下一个ChannelHandler
ctx.fireChannelRead(msg);
}
}
拥有状态
主要问题在于,对其所持有的状态的修改并非线程安全,如可通过 AtomicInteger 规避。
即用于跟踪方法调用次数的实例变量count。将这个类的一个实例添加到ChannelPipeline将极有可能在它被多个并发的Channel访问时导致问题。(这简单问题可通过使channelRead()变为同步方法来修正)。总之,只应该在确定了你的 ChannelHandler 是线程安全的时才使用 @Sharable 。
为何要共享同一个ChannelHandler ?
常见原因:用于收集跨多个 Channel 的统计信息。
4 总结
本文就是 ChannelHandlerContext 和它与其他的框架组件之间的关系的讨论。
标签:Netty,调用,ChannelHandler,必知,ChannelHandlerContext,ctx,ChannelPipeline,Channel From: https://blog.51cto.com/JavaEdge/6281011