Netty实战(六)ChannelHandler和ChannelPipeline

本文深入探讨了Netty中的ChannelHandler和ChannelPipeline。详细介绍了Channel的生命周期、ChannelHandler的生命周期及其适配器,重点讲解了ChannelInboundHandler和ChannelOutboundHandler接口的用途和方法。此外,还讨论了资源管理、ChannelPipeline的修改和事件触发,以及ChannelHandlerContext的使用和高级用法。最后,文章阐述了异常处理机制,包括入站和出站异常的处理策略。
摘要由CSDN通过智能技术生成

一、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 将数据写到远程节点时被调用

ChannelPromiseChannelFuture 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 选项重新启动你的应用程序,你将看到自己的应用程序最近被泄漏的缓冲
区被访问的位置。下面是一个典型的由单元测试产生的泄漏报告&

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timi先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值