Netty 学习记录:一个 CRUD 工程师的踩坑之旅

这是一个小白的学习记录
边学边练,把踩过的坑都记下来


先说下我为什么要学 Netty

公司项目要做一个实时通讯系统,领导让我负责技术选型。拿到需求一看,好家伙,要支持数万并发连接,还要低延迟。

我之前也就写过点 Spring Boot 的 CRUD 代码,对网络编程一窍不通。网上的文档要么太深奥,要么太简单。没办法,只能自己一点点啃,顺便把学习笔记整理出来。

如果你也是新手,希望这篇笔记能帮到你。

我的技术水平

  • Java 能看懂(CRUD 工程师)
  • Linux 命令会用(cd、ls、vim 三件套)
  • 网络编程这东西我之前也就停留在"知道 TCP/IP"的 level

坑 1:环境搭建就踩坑

❌ 我当时以为很简单

刚开始学 Netty,我想着不就是加个依赖嘛,So easy!

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha2</version>
</dependency>

结果启动项目就报错:

Exception in thread "main" java.lang.NoClassDefFoundError: io/netty/util/concurrent/EventExecutorGroup

我当时就懵了。啥?EventExecutorGroup 是个啥?

🔍 问题根源

后来查资料才明白:

  1. Netty 5.x 版本是 alpha 版本,不稳定
  2. 很多依赖包版本不兼容
  3. 网上的教程大多用的是 4.x 版本

好家伙,原来是版本惹的祸。

✅ 解决方案

换成稳定的 4.x 版本:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.87.Final</version>
</dependency>

教训:别用 alpha 版本!老老实实用稳定版。

坑 2:线程模型配置懵逼

❌ 我当时以为很简单

看了教程,说要创建两个 EventLoopGroup,我就随便写了:

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();

结果运行起来,CPU 使用率直接爆表。

我当时就急了,老板在旁边看着,我满头汗。

🔍 问题根源

后来查资料才明白:

  1. 不指定线程数时,默认是 CPU 核心数的 2 倍
  2. bossGroup 只负责接受连接,不需要太多线程
  3. 我电脑 8 核,一下就创建了 16 个线程

✅ 解决方案

正确配置线程数:

// bossGroup 只需要 1 个线程
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup 可以根据需要调整
EventLoopGroup workerGroup = new NioEventLoopGroup();

教训:线程数不是越多越好,要根据实际需求配置。

坑 3:粘包/拆包问题搞不定

❌ 我当时以为很简单

写了个 Echo 服务器,结果客户端发"Hello",服务器收到"HelloHelloHello",或者收到"Hel"、“lo”。

我当时就懵了,这是啥情况?数据怎么还能粘在一起?

🔍 问题根源

后来查资料才明白:

  1. TCP 是面向流的协议,数据会被分割或合并
  2. 这就是粘包/拆包问题
  3. 需要用解码器来处理

✅ 解决方案

使用 LengthFieldBasedFrameDecoder:

ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(
    1024, // 最大帧长度
    0,    // 长度字段偏移量
    4,    // 长度字段长度
    0,    // 长度调整值
    4     // 跳过长度字段
));

ch.pipeline().addLast(new MessageToByteEncoder<String>() {
    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        byte[] data = msg.getBytes();
        out.writeInt(data.length);
        out.writeBytes(data);
    }
});

教训:网络编程一定要处理粘包/拆包问题!

坑 4:ByteBuf 内存泄漏

❌ 我当时以为很简单

写了个处理器,直接操作 ByteBuf:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    byte[] data = new byte[buf.readableBytes()];
    buf.readBytes(data);
    String message = new String(data);
    System.out.println("Received: " + message);
    // 直接返回,没释放 buf
}

结果运行一段时间后,程序崩溃,报 OutOfMemoryError。

🔍 问题根源

后来查资料才明白:

  1. ByteBuf 需要手动释放
  2. 特别是直接内存,不释放会导致内存泄漏
  3. 应该用 SimpleChannelInboundHandler,它会自动释放

✅ 解决方案

使用 SimpleChannelInboundHandler:

public class MyHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        System.out.println("Received: " + msg);
        // 不用手动释放,SimpleChannelInboundHandler 会自动处理
    }
}

教训:ByteBuf 一定要记得释放,或者用 SimpleChannelInboundHandler。

验证步骤

1. 编译打包

mvn clean package

2. 启动服务端

java -jar netty-server.jar

3. 启动客户端

java -jar netty-client.jar

4. 测试消息发送

在客户端输入消息,确认服务端能正确接收并回显。

预期结果:消息能正确发送和接收,没有粘包/拆包问题。

总结

我踩过的坑

  1. 坑 1:用了不稳定的 Netty 5.x 版本 → 换成 4.1.87.Final
  2. 坑 2:线程数配置不当 → bossGroup 设为 1,workerGroup 默认
  3. 坑 3:没处理粘包/拆包问题 → 使用 LengthFieldBasedFrameDecoder
  4. 坑 4:ByteBuf 内存泄漏 → 使用 SimpleChannelInboundHandler

核心要点

  • 必须用稳定版本:Netty 4.1.x 是稳定版
  • 必须配置线程数:bossGroup 1 个线程足够
  • 必须处理粘包/拆包:使用合适的解码器
  • 必须注意内存管理:使用 SimpleChannelInboundHandler 或手动释放 ByteBuf

最后说两句

其实 Netty 也没多难,就是刚开始上手的时候有点懵。我也是一点点试,总算是搞定了基础的 Echo 服务器。

肯定有理解不对的地方,欢迎大佬指正。

如果你也遇到类似问题,希望这篇笔记能帮到你。


小技巧:学习 Netty 最好的方法就是跟着官方示例写代码,遇到问题就查文档。别想着一步到位,慢慢来,总能学会的!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐