Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理

6.3 Interface ChannelHandlerContext


一个ChannelHandlerContext代表了一个ChannelHandler和ChannelPipeline之间的关系,ChannelHandlerContext创建于ChannelHandler被载入到ChannelPipeline的时候,ChannelHandlerContext主要功能是管理在同一ChannelPipeline中各个ChannelHandler的交互


ChannelHandlerContext有很多方法,其中的一些方法也出现在Channel和ChannelPipeline中,但是相同的方法却有一些不同的效果,如果你在Channel或者在ChannelPipeline实例中实现这些方法,它们会传播到整个管道,同样的方法如果被ChannelHandlerContext执行的时候,那么它会在当前关联的ChannelHandler中开始执行,然后只会传播到管道中符合条件(符合方向的有兴趣的)的下一个ChannelHandler处理


表6.10总结了ChannelHandlerContext的APIs

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第1张图片

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第2张图片

当你使用ChannelHandlerContext的时候,请牢记以下几点原则:

1)与ChannelHandlerContext关联的ChannelHandler是不会改变的,所以如果对其引用做一份缓存是安全的

2)ChannelHandlerContext的方法与定义在其他class中的方法是不一样的,它有着较短的生命周期,这个可以被用来尽可能的提高应用的性能


6.3.1 Using ChannelHandlerContext


在这个小节中,我们将讨论如何使用ChannelHandlerContext并且查看由ChannelHandlerContext,Channel,ChannelPipeline提供的一些方法,图6.4向你展示了它们三者之间的关系

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第3张图片

在下面的代码清单中,你将从ChannelHandlerContext中获取一个Channel的引用,通过调用Channel的write的方法写在管道里会引起一个写入流

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第4张图片

下面的代码清单也展示了一个相似的例子,这次也是用ChannelPipeline写入,我们可以从ChannelHandlerContext中获取的ChannelPipeline引用


代码清单6.6和代码清单6.7是很相似的,都如图6.5所示,你必须注意到通过Channel或者ChannelPipeline调用write方法都会使事件沿着管道传播,这一点是很重要的,从一个Channel到下一个Channel的移动是由ChannelHandlerContext管控的

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第5张图片

为什么有时候我们需要在ChannelPipeline中的某些具体的地方开始传播一个事件呢?

1)减少事件在那些对该事件不感兴趣的处理器传播的损耗

2)防止那些“感兴趣”的处理器处理后产生不好的影响


为了从一个指定的ChannelHandler开始执行处理,你必须指定一个ChannelHandler处理器之前的那个ChannelHandlerContext,这个ChannelHandlerContext会调用与它关联的那个ChannelHandler,这个ChannelHandler也就是我们指定的那个处理器


下面的代码清单和图6.6说明了这个使用的方法

如图6.6所示,信息从图中第二个ChannelHandler中开始在ChannelPipeline中传播,跳过了前一个


我们刚才描述使用的是一个很常见的一种使用方式,调用一个特定的ChannelHandler实例是一个非常有用的操作方式


Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第6张图片

6.3.2 Advanced uses of ChannelHandler and ChannelHandlerContext


在我们之前看见的6.6清单所示,我们通过ChannelHandlerContext的pipeline方法获取到一个ChannelPipeline的引用,这里可以在运行的时候操作管道中的ChannelHandler,这里可以在复杂的业务场景中用到,例如,你可以通过增加一个ChannelHandler到管道中来支持动态协议的改变


另一个使用的好处就是获取一个ChannelHandlerContext的一个引用,这个引用的缓存可以支持“延迟使用”,这样可以发生在ChannelHandler之外甚至可以给不同的线程都执行,下面的代码清单展示了这种模式

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第7张图片

因为一个ChannelHandler可以属于多个ChannelPipeline,它也可以绑定多个ChannelHandlerContext实例,如果一个ChannelHandler想要有这样的功能,就必须以@Sharable注解注释这个ChannelHandler,否则,尝试将其加入到不止一个ChannelPipeline中去的时候,会报出异常,很明显,使用这样的支持多线程的channel你必须保证该类是线程安全的,无状态的


下面的代码清单展示了这个模式一个正确的实现

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第8张图片

先前的ChannelHandler实现了满足其被在多个管道使用时的多个需求,顾名思义,它被@Sharable注解了自己,并且它不包含任何的状态,相反,下面的6.11代码清单就可能引起一些问题:

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第9张图片

这个代码的问题是因为它是有状态的,因为有一个count的变量,这个变量会计算该方法的调用,增加这个类的实例到ChannelPipeline中去就可能会出现错误,当它被并发的channel触发的时候出现


总而言之,你必须保证你的ChannelHandler是线程安全的在你使用@Sharable的时候

WHY SHARE A CHANNELHANDLER? 在多个ChannelPipeline中使用同一个ChannelHandler的一个正常理由是多个Channel的统计信息


我们对ChannelHandlerContext的讨论做了总结,并且讲解了它与其他组件之间的关系,下一个小节,我们讨论一下异常处理


6.4 Exception handling


异常处理对于任意一个重大的系统都是一个很重要的模块,它可以以多种方式去完成这个模块,相应的,Netty提供了一些输入输出处理时产生异常的一些处理选项,这个小节将帮助你理解这些方式,并找出最合适满足你需要的处理方式


6.4.1 Handling inbound exceptions

在输入事件处理过程中一个异常抛出时,这个异常将会在触发该异常的ChannelInboundHandler的地方开始在ChannelPipeline传播,为了处理这样的异常,你需要在你的ChannelPipeline实现中重写如下的方法



下面的代码清单向你展示一个简单的异常处理例子:关闭channel然后打印出异常信息

因为发生的异常可以继续沿着输入的方向传播,这就保证了无论异常在哪里发生,所有在ChannelPipeline中的业务逻辑都会处理,且所有的异常都会被捕获处理


关于如何处理异常更多的情况下是取决你具体的应用程序,你可能会关闭channel连接,也可能去尝试去恢复,当然,如果你没有实现任何异常处理的方法,Netty将会记录异常没有被处理


总结如下:

1)默认的ChannelHandler的exceptionCaught的实现会把异常传给管道中的下一个处理器

2)如果一个异常到达了一个管道的结尾,它将被记录为未被处理

3)来定义自定义的处理,你重写了exceptionCaught方法,这将是你自己的决定你是否决定将其接着传播到下游处理器


6.4.2 Handling outbound exceptions


处理输出操作的正常实现或者异常问题是基于以下几点列出的机制:

1)每一个输出操作返回一个ChannelFuture,注册到ChannelFuture上的ChannelFutureListener会被通知操作是否成功或者失败当操作完成的时候

2)几乎所有的ChannelOutboundHandler都会传递一个ChannelPromise实例,作为ChannelFuture的子类,它也可以被监听器管控来获取异步通知,但是ChannelPromise为实时通知提供可写的方法



在ChannelFuture实例中增加一个ChannelFutureListener是为了调用addListener方法,有两种方法可以完成这个实现,一个最最常用的方式是调用由输出操作返回的ChannelFuture的addListener方法

下面的代码清单使用了这个方法来增加一个ChannelFutureListener,用来打印一次然后关闭Channel

第二个方式是通过在ChannelOutboundHandler方法中作为参数的传递的ChannelPromise增加一个ChannelFutureListener,下面的代码清单展示了与上面代码有相同功能的实现

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第10张图片

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理_第11张图片

TIPS:ChannelPromise的写方法

通过调用ChannelPromise的setSuccess或者setFailure方法,你可以是调用者在ChannelHandler返回的时候,立马知道操作的是否成功


怎么才知道两个方式哪个更好呢?如果你想要更仔细地处理异常,那么你会发现使用代码清单6.13中更加合适,但是如果你想更加专业的处理异常,你会发现6.14显得更加简单


如果ChannelOutboundHandler自己本身发生了异常,那么会发生什么呢?这种情况下,Netty本身会通知注册到相对应的ChannelPromise上的任意监听器


6.5 Summary

在这个章节,我们讨论了Netty的数据处理组件ChannelHandler,我们讨论了多个ChannelHandler链在一起,讨论了ChannelInboundHandler和ChannelOutboundHandler如何与ChannelPipeline交互的


下一个章节我们讨论Netty的编码抽象,这个将比直接使用底层的ChannelHandler实现来编写编码和解码更加简单



你可能感兴趣的:(Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理)