6 channelHandler和ChannelPipeline
6.1.1 Channel 的生命周期
Channel 的正常生命周期如图6-1 所示。当这些状态发生改变时,将会生成对应的事件。
这些事件将会被转发给ChannelPipeline 中的ChannelHandler,其可以随后对它们做出
响应。
由于SimpleChannelInboundHandler 会自动释放资源,所以你不应该存储指向任何消
息的引用供将来使用,因为这些引用都将会失效。
每当通过调用ChannelInboundHandler.channelRead()或者ChannelOutbound-
Handler.write()方法来处理数据时,你都需要确保没有任何的资源泄漏。
消费入站消息的简单方式 由于消费入站数据是一项常规任务,所以Netty 提供了一个特殊的被
称为SimpleChannelInboundHandler 的ChannelInboundHandler 实现。这个实现会在消
息被channelRead0()方法消费之后自动释放消息。
这个地方要重点注意:
Channel 或ChannelPipeline 上的write()方法将一直传播事件通过整个ChannelPipeline,
但是ChannelHandlerContext的write()方法将传播给下个channelHandler。
/** * 代码清单 6-9 缓存到 ChannelHandlerContext 的引用 * * @author <a href="mailto:norman.maurer@gmail.com">Norman Maurer</a> */ public class WriteHandler extends ChannelHandlerAdapter { private ChannelHandlerContext ctx; @Override public void handlerAdded(ChannelHandlerContext ctx) { //存储到 ChannelHandlerContext的引用以供稍后使用 this.ctx = ctx; } public void send(String msg) { //使用之前存储的到 ChannelHandlerContext的引用来发送消息 ctx.writeAndFlush(msg); } }
这段代码的问题在于它拥有状态①,即用于跟踪方法调用次数的实例变量count。将这个类 的一个实例添加到ChannelPipeline将极有可能在它被多个并发的Channel访问时导致问 题。(当然,这个简单的问题可以通过使channelRead()方法变为同步方法来修正。) 总之,只应该在确定了你的ChannelHandler 是线程安全的时才使用@Sharable 注解。
总结一下:
ChannelHandler.exceptionCaught()的默认实现是简单地将当前异常转发给
ChannelPipeline 中的下一个ChannelHandler;
如果异常到达了ChannelPipeline 的尾端,它将会被记录为未被处理;
要想定义自定义的处理逻辑,你需要重写exceptionCaught()方法。然后你需要决定
是否需要将该异常传播出去。
6.4.2 处理出站异常
为何选择一种方式而不是另一种呢?对于细致的异常处理,你可能会发现,在调用出站操 作时添加ChannelFutureListener 更合适,如代码清单6-13 所示。而对于一般的异常处 理,你可能会发现,代码清单6-14 所示的自定义的ChannelOutboundHandler 实现的方式 更加的简单。 如果你的ChannelOutboundHandler 本身抛出了异常会发生什么呢?在这种情况下, Netty 本身会通知任何已经注册到对应ChannelPromise 的监听器。