ChannelHandlerContext接口
ChannelHandlerContext代表了一个ChannelHandler和一个ChannelPipeline之间的关系,它在ChannelHandler被添加到ChannelPipeline时被创建。ChannelHandlerContext的主要功能是管理它对应的ChannelHandler和属于同一个ChannelPipeline的其他ChannelHandler之间的交互。
ChannelHandlerContext有很多方法,其中一些方法Channel和ChannelPipeline也有,但是有些区别。如果你在Channel或者ChannelPipeline实例上调用这些方法,它们的调用会穿过整个pipeline。而在ChannelHandlerContext上调用的同样的方法,仅仅从当前ChannelHandler开始,走到pipeline中下一个可以处理这个event的ChannelHandler。
在使用ChannelHandlerContext API时,请牢记下面几点:
- 一个ChannelHandler绑定的ChannelHandlerContext 永远不会改变,所以把它的引用缓存起来是安全的。
- ChannelHandlerContext的一些方法和其他类(Channel和ChannelPipeline)的方法名字相似,但是ChannelHandlerContext的方法采用了更短的event传递路程。我们应该尽可能利用这一点来实现最好的性能。
使用ChannelHandlerContext
代码1.0 Channel,ChannelPipeline,ChannelHandler和ChannelHandlerContext之间的关系
|
|
在下面的这段代码里,你从ChannelHandlerContext中获取了Channel的引用。在Channel上调用write()
会让写event穿过整个pipeline。
代码1.1 从ChannelHandlerContext从获取Channel
|
|
下面这段代码是一个类似的例子,但是这次却写数据到一个ChannelPipeline。ChannelPipeline的引用是从ChannelHandlerContext中获取的。
代码1.2 从ChannelHandlerContext从获取ChannelPipeline
|
|
上述两个代码中的event传递路径是类似的。重点要注意的是,虽然不管是Channel还是ChannelPipeline上调用的write()
都是穿过整个pipeline传递event的,但是在ChannelHandler里,从一个handler走到下一个,是通过ChannelHandlerContext的。
那为什么你可能会需要在ChannelPipeline某个特定的位置开始传送一个event呢?
- 减少因为让event穿过那些对它不感兴趣的ChannelHandler而带来的开销
- 避免event被那些可能对它感兴趣的handler处理
为了从某个特定的ChannelHandler开始处理,你必须获取前一个ChannelHandler绑定的ChannelHandlerContext的引用。这个ChannelHandlerContext会调用它所绑定的ChannelHandler的下一个handler。
下面的代码说明了这个用法。
代码1.3 调用ChannelHandlerContext上的write()
|
|
如代码所示,穿过ChannelPipeline的消息从下一个ChannelHandler开始,忽略所有之前的ChannelHandler。
我们刚刚描述的这个用例很常见,当被用来在一个特定的ChannelHandler实例上调用一些操作时,这个做法特别有用。
ChannelHandler和ChannelHandlerContext的高级用法
你可以通过调用ChannelHandlerContext的pipeline方法来获取绑定的ChannelPipeline引用。这实现了对ChannelHandler在运行时的操控,可以实现一些更为复杂的设计。比如,你可以添加一个ChannelHandler到一个pipeline来支持一个动态的协议转换。
其他的高级应用还包括把一个ChannelHandlerContext的引用放入缓存,待后来使用。稍后在使用该引用时,可能不在ChannelHandler方法内,甚至可能在另一个线程里。这段代码说明了如何用这种模式来触发一个event。
代码1.4,缓存一个ChannelHandlerContext
|
|
因为一个ChannelHandler可以属于多个ChannelPipeline,所以它可以绑定多个ChannelHandlerContext实例。想要实现这个用法的ChannelHandler必须加上注解@Sharable
;否则,试图将它添加到多个ChannelPipeline时会触发一个异常。显然,想要在多并发channel(也就是连接)中保证线程安全,这样的一个ChannelHandler必须是线程安全的类。
代码1.5 可共享的ChannelHandler
|
|
1.5中的ChannelHandler实现满足放入多个pipeline的条件;也就是说,它加上了@Sharable
注解,而且没有包含任何状态。相反地,1.6中的代码会带来问题。
代码1.6 @Sharable
的无效用法
|
|
这段代码的问题是,它是有状态的,就是用来跟踪方法调用次数的实例变量count。把这个类的一个实例加到ChannelPipeline中,当它被并发的channel获取时,就很有可能出错。(当然,让channelRead()
变成同步方法就可以修正这个简单的例子。)
总之,只有在你确信你的ChannelHandler是线程安全的情况下,才使用@Sharable
。
为什么要共享一个ChannelHandler?
将一个ChannelHandler装入多个ChannelPipeline的一个常见原因,是收集多个Channel的统计数据。