【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder

目录

如何使用?

[1. 示例代码(基于Netty)](#1. 示例代码(基于Netty))

[2. 关键参数解释](#2. 关键参数解释)

[3. 协议格式示例](#3. 协议格式示例)

[4. 常见配置场景](#4. 常见配置场景)

场景1:长度字段包含自身

场景2:长度字段在消息中间

[5. 注意事项](#5. 注意事项)

举个例子

完整示例:客户端与服务端交互流程

[1. 服务端代码(含响应)](#1. 服务端代码(含响应))

[2. 客户端代码(含编码器)](#2. 客户端代码(含编码器))

[3. 执行流程说明](#3. 执行流程说明)

[4. 网络包结构示意图](#4. 网络包结构示意图)

[5. 关键点总结](#5. 关键点总结)


如何使用?

以下是使用 LengthFieldBasedFrameDecoder 解决 TCP 粘包/拆包问题的 完整代码示例关键解释


1. 示例代码(基于Netty)

复制代码
// Server端代码示例
public class NettyServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         protected void initChannel(SocketChannel ch) {
                             // 关键:添加 LengthFieldBasedFrameDecoder
                             ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(
                                     1024,    // maxFrameLength(最大帧长度)
                                     0,       // lengthFieldOffset(长度字段偏移量)
                                     4,       // lengthFieldLength(长度字段占4字节)
                                     0,       // lengthAdjustment(长度调整值)
                                     4        // initialBytesToStrip(跳过前4字节,因为长度字段已解析)
                             ));
                             // 将ByteBuf转为String(按需替换为实际解码器)
                             ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
                             // 自定义业务处理器
                             ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                                 @Override
                                 protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                                     System.out.println("Received message: " + msg);
                                 }
                             });
                         }
                     });
            ChannelFuture future = bootstrap.bind(8080).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

2. 关键参数解释

LengthFieldBasedFrameDecoder 的构造函数参数如下:

|-----------------------|--------------------------------|
| 参数 | 说明 |
| maxFrameLength | 允许的最大帧长度(防止内存溢出) |
| lengthFieldOffset | 长度字段的起始偏移量(通常为0) |
| lengthFieldLength | 长度字段占用的字节数(例如4字节表示int) |
| lengthAdjustment | 长度字段值后的内容长度调整(若长度字段包含自身长度,需调整) |
| initialBytesToStrip | 解析后跳过的字节数(例如跳过长度字段本身) |


3. 协议格式示例

假设自定义协议格式如下(长度字段在前):

复制代码
+--------+----------------+
| Length |   Actual Data  |
| 4字节  |   (变长内容)    |
+--------+----------------+

4. 常见配置场景

场景1:长度字段包含自身
复制代码
// 长度字段包含自身(如总长度= Length字段长度 + 数据长度)
new LengthFieldBasedFrameDecoder(1024, 0, 4, -4, 0);
// lengthAdjustment = -4(扣除长度字段自身占用的4字节)
场景2:长度字段在消息中间
复制代码
// 消息格式:[Header][Length][Data]
new LengthFieldBasedFrameDecoder(1024, 2, 4, 0, 6);
// lengthFieldOffset=2(跳过Header的2字节)
// initialBytesToStrip=6(跳过Header+Length字段)

5. 注意事项

  1. 参数匹配协议:必须与协议中长度字段的位置和计算方式一致。
  2. 编解码顺序LengthFieldBasedFrameDecoder 需作为第一个解码器添加到Pipeline。
  3. 异常处理 :建议配合 ExceptionHandler 处理解码失败的情况。

通过这种方式,Netty 会自动根据长度字段切分完整的数据包,彻底解决粘包/拆包问题

举个例子


完整示例:客户端与服务端交互流程

1. 服务端代码(含响应)
复制代码
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         protected void initChannel(SocketChannel ch) {
                             // 添加长度字段解码器
                             ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
                             // 字符串解码器
                             ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
                             // 业务处理器(返回响应)
                             ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                                 @Override
                                 protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                                     System.out.println("[Server] Received: " + msg);
                                     // 返回响应(添加长度前缀)
                                     ctx.writeAndFlush("ACK: " + msg);
                                 }
                             });
                         }
                     });
            ChannelFuture future = bootstrap.bind(8080).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
2. 客户端代码(含编码器)
复制代码
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                     .channel(NioSocketChannel.class)
                     .handler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         protected void initChannel(SocketChannel ch) {
                             // 添加编码器(为消息添加长度前缀)
                             ch.pipeline().addLast(new MessageToByteEncoder<String>() {
                                 @Override
                                 protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {
                                     byte[] bytes = msg.getBytes(CharsetUtil.UTF_8);
                                     out.writeInt(bytes.length); // 写入4字节长度字段
                                     out.writeBytes(bytes);      // 写入实际数据
                                 }
                             });
                             // 响应解码器(与服务端解码器对称)
                             ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
                             ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
                             // 业务处理器(打印响应)
                             ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                                 @Override
                                 protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                                     System.out.println("[Client] Received: " + msg);
                                 }
                             });
                         }
                     });
            ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
            // 发送两条测试消息(自动处理粘包)
            future.channel().writeAndFlush("Hello Netty");
            future.channel().writeAndFlush("Test Message");
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

3. 执行流程说明

  1. 客户端发送消息
    • 编码器将字符串转换为 长度字段(4字节) + 实际数据 的二进制格式。

    • 示例消息 "Hello Netty" 的传输格式:

      +----------+-----------------+
      | 0x00000B | "Hello Netty" | // 0x00000B = 11字节(字符串长度)
      +----------+-----------------+

  1. 服务端解析消息
    • LengthFieldBasedFrameDecoder 根据长度字段切分完整数据包。
    • StringDecoder 将二进制数据转为字符串,业务处理器打印并返回响应。
  1. 客户端接收响应
    • 服务端返回的 "ACK: Hello Netty" 同样通过长度字段编码。
    • 客户端解码器解析后打印响应信息。

4. 网络包结构示意图

复制代码
客户端发送:
[Length=11][Data="Hello Netty"][Length=12][Data="Test Message"]

服务端接收:
[Length=11][Data="Hello Netty"] → 完整解析为独立消息
[Length=12][Data="Test Message"] → 完整解析为独立消息

服务端响应:
[Length=16][Data="ACK: Hello Netty"]
[Length=17][Data="ACK: Test Message"]

5. 关键点总结

  • 编码对称性:客户端和服务端的编解码器需匹配(长度字段位置一致)。
  • 自动分包LengthFieldBasedFrameDecoder 自动处理TCP流中的粘包/拆包。
  • 性能保障:基于长度字段的解析效率极高,适合高频数据传输场景。

运行示例后,你将在控制台看到完整的请求-响应日志,验证粘包问题的解决效果。

相关推荐
Swift社区1 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT2 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy2 小时前
JVM——Java虚拟机学习
java·jvm·学习
只因在人海中多看了你一眼3 小时前
B.50.10.09-RPC核心原理与电商应用
qt·网络协议·rpc
seabirdssss3 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续4 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0444 小时前
ReAct模式解读
java·ai
轮到我狗叫了4 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
小鸟啄米4 小时前
Elixir通过Onvif协议控制IP摄像机,扩展ExOnvif的摄像头停止移动 Stop 功能
网络协议·elixir·onvif