背景:netty执行链中有一串自定义的handler,目前我想要在中间再加上一个pingPonghandler来进行控制帧的处理,从而避免ng的读写超时(客户要求,与他们建立的通道一直连接,不进行断连,从而需要考虑ng的问题);
当我添加如下代码后报错:io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1,ByteBuf
的引用计数refCnt为0;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
public class PingPongHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
if (frame instanceof PingWebSocketFrame) {
// 接收到PING帧
System.out.println("Received PING frame");
// 发送PONG帧作为响应
ctx.channel().writeAndFlush(new PongWebSocketFrame(Unpooled.buffer(0)));
} else if (frame instanceof PongWebSocketFrame) {
// 接收到PONG帧
System.out.println("Received PONG frame");
} else {
// 其他类型的帧,继续传递给下一个处理器
ctx.fireChannelRead(frame);
}
}
}
ByteBuf echoMsg = frame.content();
echoMsg.refCnt(); 获取引用计数
解决办法1:(新手不推荐,最好还是使用netty的自动释放逻辑,避免处理不当)
echoMsg.retain(); //手动进行引用计数加1,并在使用完成之后释放调
解决方法2:关闭SimpleChannelInboundHandler的自动释放(推荐)
package com.iflytek.iptv.websocket;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
public class PingPongHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
// 构造函数中设置autoRelease为false
public PingPongHandler() {
super(false);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
if (frame instanceof PingWebSocketFrame) {
// 接收到PING帧
System.out.println("Received PING frame");
// 发送PONG帧作为响应
ctx.channel().writeAndFlush(new PongWebSocketFrame(Unpooled.buffer(0)));
} else if (frame instanceof PongWebSocketFrame) {
// 接收到PONG帧
System.out.println("Received PONG frame");
} else {
// 其他类型的帧,继续传递给下一个处理器
ctx.fireChannelRead(frame);
}
}
}
解决方法3:继承ChannelInboundHandlerAdapter ,默认不释放(推荐)
package com.iflytek.iptv.websocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PingPongHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = LoggerFactory.getLogger(PingPongHandler1.class);
@Override
public void channelRead(ChannelHandlerContext ctx, Object frame) throws Exception {
if (frame instanceof PingWebSocketFrame) {
// 接收到PING帧
log.info("Received ping frame, connection is alive");
// 发送PONG帧作为响应
// ctx.channel().writeAndFlush(new PongWebSocketFrame(Unpooled.buffer(0)));
} else if (frame instanceof PongWebSocketFrame) {
// 接收到PONG帧
log.info("Received pong frame, connection is alive");
} else {
// 其他类型的帧,继续传递给下一个处理
ctx.fireChannelRead(frame);
}
}
}
问题分析:
在Netty中,ByteBuf
是一个字节容器,它支持多种数据读写操作,并且其内部使用引用计数(refCnt)来管理资源的生命周期。引用计数用于确保资源(如内存)在不再需要时能够被适当释放,防止内存泄漏。
当你提到继承 SimpleChannelInboundHandler<WebSocketFrame>
和 ChannelInboundHandlerAdapter
并使用 ctx.fireChannelRead(frame)
时,ByteBuf
的引用计数表现不同,这主要是由于 SimpleChannelInboundHandler
和 ChannelInboundHandlerAdapter
处理 ByteBuf
的方式有所区别。
SimpleChannelInboundHandler
SimpleChannelInboundHandler<T>
是一个方便的类,它专门为处理特定类型的入站消息而设计。它会在消息传递到下一个 ChannelHandler
之前自动释放(release()
)传入的消息对象(如果消息实现了 ReferenceCounted
接口,如 ByteBuf
)。这是因为它假设在 channelRead0(ChannelHandlerContext ctx, T msg)
方法中处理完消息后,当前处理器不需要再保留对该消息的引用。
因此,当你在 SimpleChannelInboundHandler
的 channelRead0
方法中调用 ctx.fireChannelRead(frame)
(这里的 frame
是 WebSocketFrame
的一个实例,而 WebSocketFrame
内部通常包含 ByteBuf
),由于 SimpleChannelInboundHandler
的设计,Netty 会在将消息传递给下一个处理器之前自动调用 release()
。这就是为什么你看到引用计数减少的原因。
ChannelInboundHandlerAdapter
相反,ChannelInboundHandlerAdapter
是一个更通用的基类,它不假设你会在方法执行后立即释放消息。因此,当你在 ChannelInboundHandlerAdapter
的 channelRead
方法中调用 ctx.fireChannelRead(frame)
时,Netty 不会自动调用 release()
。这意呀着你需要自己管理 ByteBuf
的生命周期,确保在不再需要时调用 release()
,否则可能导致内存泄漏。
应该选择哪个?
- 如果你处理的是实现了
ReferenceCounted
接口的对象(如ByteBuf
),并且你的处理器在处理完消息后不再需要它,那么SimpleChannelInboundHandler
是一个不错的选择,因为它可以自动管理引用计数。 - 如果你需要更细粒度的控制,比如你计划在多个处理器之间共享
ByteBuf
,或者你的处理器只是将消息传递给下一个处理器而不立即处理它,那么ChannelInboundHandlerAdapter
可能是更好的选择。但是,你需要确保自己正确地管理引用计数,避免内存泄漏。
结论
不是所有的中间处理器都必须继承 ChannelInboundHandlerAdapter
。如果你处理的消息类型是特定的,并且你可以接受 SimpleChannelInboundHandler
的自动释放行为,那么使用它可能更方便。然而,如果你需要更复杂的处理逻辑或消息共享,那么 ChannelInboundHandlerAdapter
将提供更多的灵活性。重要的是要根据你的具体需求来选择适当的处理器类。