跳到主要内容

常见的坑

未释放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等方法是不会被调用的,需要特别注意!