ChannelHandler和ChannelPipeline
一、ChannelHandler
1.1 Channel 的生命周期
Channel 主要有四个生命周期向下表所示:
状 态 | 描 述 |
---|---|
ChannelUnregistered | Channel 已经被创建,但还未注册到 EventLoop |
ChannelRegistered | Channel 已经被注册到了 EventLoop |
ChannelActive | Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了 |
ChannelInactive | Channel 没有连接到远程节点 |
当channel的生命周期发生改变后,会生成对应的事件。这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler,其可以随后对它们做出响应。如图示:
1.2 ChannelHandler 的生命周期
ChannelHandler 的生命周期发生在ChannelHandler被添加到 ChannelPipeline 中或者被从
ChannelPipeline 中移除时。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。
类 型 | 描 述 |
---|---|
handlerAdded | 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用 |
handlerRemoved | 当从 ChannelPipeline 中移除 ChannelHandler 时被调用 |
exceptionCaught | 当处理过程中在 ChannelPipeline 中有错误产生时被调用 |
1.3 ChannelInboundHandler 接口
ChannelInboundHandler用以处理入站数据以及各种状态变化。
下面是一些常用的ChannelInboundHandler方法,它们一般在数据被接收时或者与其对应的 Channel 状态发生改变时被调用。
类 型 | 描 述 |
---|---|
channelRegistered | 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用 |
channelUnregistered | 当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用 |
channelActive | 当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪 |
channelInactive | 当 Channel 离开活动状态并且不再连接它的远程节点时被调用 |
channelReadComplete | 当Channel上的一个读操作完成时被调用 |
channelRead | 当从 Channel 读取数据时被调用 |
ChannelWritabilityChanged | 当 Channel 的可写状态发生改变时被调用。用户可以确保写操作不会完成得太快(以避免发生 OutOfMemoryError)或者可以在 Channel 变为再次可写时恢复写入。可以通过调用 Channel 的 isWritable()方法来检测Channel 的可写性。与可写性相关的阈值可以通过 Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法来设置 |
userEventTriggered | 当 ChannelnboundHandler.fireUserEventTriggered |
当某个 ChannelInboundHandler 的实现重写 channelRead()方法时,它将负责显式地释放与池化的ByteBuf 实例相关的内存。Netty 为此提供了一个实用方法 ReferenceCountUtil.release()。
如下面代码所示:
@Sharable
//扩展ChannelInboundHandlerAdapter
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//丢弃读取到的消息
ReferenceCountUtil.release(msg);
}
}
Netty 将使用 WARN 级别的日志消息记录未释放的资源,使得可以非常简单地在代码中发现违规的实例。但是以这种方式管理资源可能很繁琐。一个更加简单的方式是使用 SimpleChannelInboundHandler。
像下面这个代码:
@Sharable
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
Object msg) {
// 不需要任何显式的资源释放
}
}
由于 SimpleChannelInboundHandler 会自动释放资源,所以你不应该存储指向任何消息的引用供将来使用,因为这些引用都将会失效。(PS:Netty实战(二)中我们提到过客户端为什么继承它的原因就是因为它会自动的释放资源)
1.4 ChannelOutboundHandler 接口
ChannelOutboundHandler用以处理出站数据并且允许拦截所有的操作。
当它处理出站操作和数据时它的方法将被 Channel、ChannelPipeline 以及 ChannelHandlerContext 调用。
ChannelOutboundHandler 的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。例如,如果到远程节点的写入被暂停了,那么你可以推迟冲刷操作并在稍后继续。
下面是ChannelOutboundHandler本身所定义的所有方法:
类 型 | 描 述 |
---|---|
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) | 当请求将 Channel 绑定到本地地址时被调用 |
connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise) | 当请求将 Channel 连接到远程节点时被调用 |
disconnect(ChannelHandlerContext,ChannelPromise) | 当请求将 Channel 从远程节点断开时被调用 |
close(ChannelHandlerContext,ChannelPromise) | 当请求关闭 Channel 时被调用 |
deregister(ChannelHandlerContext,ChannelPromise) | 当请求将 Channel 从它的 EventLoop 注销时被调用 |
read(ChannelHandlerContext) | 当请求从 Channel 读取更多的数据时被调用 |
flush(ChannelHandlerContext) | 当请求通过 Channel 将入队数据冲刷到远程节点时被调用 |
write(ChannelHandlerContext,Object,ChannelPromise) | 当请求通过 Channel 将数据写到远程节点时被调用 |
ChannelPromise与ChannelFuture ChannelOutboundHandler中的大部分方法都需要一个
ChannelPromise参数,以便在操作完成时得到通知。ChannelPromise是ChannelFuture的一个子类,其定义了一些可写的方法,如setSuccess()和setFailure(),从而使ChannelFuture不可变。
1.5 ChannelHandler 适配器
我们可以使用ChannelInboundHandlerAdapter 和
ChannelOutboundHandlerAdapter类作为自己的 ChannelHandler 的起始点
。
这两个适配器分别提供了 ChannelInboundHandler和 ChannelOutboundHandler 的基本实现。通过扩展抽象类 ChannelHandlerAdapter,它们获得了它们共同的超接口 ChannelHandler 的方法。
像下面这样:
ChannelHandlerAdapter 还提供了实用方法 isSharable()。如果其对应的实现被标注为 Sharable,那么这个方法将返回 true,表示它可以被添加到多个 ChannelPipeline中。
在 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 中所提供的方法体调用了其相关联的 ChannelHandlerContext 上的等效方法,从而将事件转发到了 ChannelPipeline 中的下一个ChannelHandler 中。
当我们要想在自己的 ChannelHandler 中使用这些适配器类时,只需要简单地扩展它们,并且重写那些你想要自定义的方法。
1.6 资源管理
每当通过调用 ChannelInboundHandler.channelRead()或者
ChannelOutboundHandler.write()方法来处理数据时,都需要确保没有任何的资源泄漏。
Netty 使用引用计数来处理池化的 ByteBuf。所以在完全使用完某个ByteBuf 后,调整其引用计数是很重要的。
Netty提供了class ResourceLeakDetector 帮助我们诊断潜在的(资源泄漏)问题。它将对你应用程序的缓冲区分配做大约 1%的采样来检测内存泄露。相关的开销是非常小的。
如果检测到了内存泄露,将会产生类似于下面的日志消息:
LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable
advanced leak reporting to find out where the leak occurred. To enable
advanced leak reporting, specify the JVM option
'-Dio.netty.leakDetectionLevel=ADVANCED' or call
ResourceLeakDetector.setLevel().
Netty 目前定义了 4 种泄漏检测级别,像下面这样:
级 别 | 描 述 |
---|---|
DISABLED | 禁用泄漏检测。只有在详尽的测试之后才应设置为这个值 |
SIMPLE | 使用 1%的默认采样率检测并报告任何发现的泄露。这是默认级别,适合绝大部分的情况 |
ADVANCED | 使用默认的采样率,报告所发现的任何的泄露以及对应的消息被访问的位置 |
PARANOID | 类似于 ADVANCED,但是其将会对每次(对消息的)访问都进行采样。这对性能将会有很大的影响,应该只在调试阶段使用 |
内存泄露检测级别可以通过将下面的 Java 系统属性设置为表中的一个值来定义:
java -Dio.netty.leakDetectionLevel=ADVANCED
如果带着该 JVM 选项重新启动你的应用程序,你将看到自己的应用程序最近被泄漏的缓冲
区被访问的位置。下面是一个典型的由单元测试产生的泄漏报告&