常见的坑
未释放ByteBuf
- 坑:未调用release()导致堆外内存泄漏
- 解决:使用ReferenceCountUtil.release()或try-finally块
未处理半包/粘包
TCP是面向流的协议,没有消息边界,数据会被拆分为多个数据包传输。以下情况需要处理粘包/半包:
短连接频繁通信:客户端快速发送多个小数据包,服务端可能一次性读取到多个包(粘包)。 大数据包传输:单个数据包超过TCP缓冲区大小(如/),会被拆分成多个包(半包)。 滑动窗口机制:TCP为提高效率会合并小包(Nagle算法),导致粘包。
- 粘包:TCP 是面向流的协议,发送方可能将多个消息合并发送,或者接收方一次性接收到多个消息,导致消息边界模糊。例如,客户端发送 "Hello" 和 "World",服务器可能收到 "HelloWorld"。
- 半包:接收方可能只收到消息的一部分,例如,客户端发送 "HelloWorld",服务器可能先收到 "Hel",随后收到 "loWorld"。
可以采用内置的FixedLengthFrameDecoder(固定长度协议)、LengthFieldBasedFrameDecoder(变长字段协议)、DelimiterBasedFrameDecoder(分隔符)、LineBasedFrameDecoder(换行符)解码器解决。 也可自定义解码器解决,例如常见的:消息头+消息体结构
何时无需处理粘包和半包问题? UDP协议:UDP本身有消息边界,不会粘包。 HTTP协议:HTTP/1.1本身通过Content-Length或分块编码标识边界,Netty的HttpObjectAggregator已处理
总结:只要使用TCP且消息边界需要明确时,就必须考虑粘包/半包问题
心跳未配置
- 坑:长连接无心跳导致假死
- 解决:添加IdleStateHandler
客户端频繁断开导致资源泄漏
1、未正确释放ByteBuf(堆外内存泄漏)
ByteBuf buffer = ctx.alloc().buffer(); // 分配内存
// 未调用 buffer.release(),导致泄漏
2、Channel未关闭或未清理Pipeline
客户端断开时,若未调用channel.close()或未移除自定义ChannelHandler,可能导致:
- 线程池资源未释放。
- 内存中的Channel对象堆积
@Override
public void channelInactive(ChannelHandlerContext ctx) {
ctx.channel().close(); // 关闭Channel
cleanupResources(); // 自定义清理逻辑
}
3、心跳机制缺失 未配置心跳检测,无法及时感知断开连接,导致资源滞留
//添加心跳机制
pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartbeatHandler()); // 自定义心跳处理
4、线程池或EventLoop未正确关闭 频繁创建/销毁EventLoopGroup会导致线程泄漏
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}));
发送的数据格式不符合服务端handler处理格式
注:抽象层级高的Handler除外,没有对类型进行匹配。下面是一些需要限制参数类型的!
- SimpleChannelInboundHandler 这种情况下handler的channelRead等方法是不会被调用的,需要特别注意!