LengthFieldBasedFrameDecoder 详细用法

摘自:https://blog.youkuaiyun.com/mzjnumber1/article/details/106663193

1、介绍
大多数的协议(私有或者公有),协议头中会携带长度字段,用于标识消息体或者整包消息的长度,例如SMPP、HTTP协议等。由于基于长度解码需求 的通用性,Netty提供了LengthFieldBasedFrameDecoder/LengthFieldPrepender,自动屏蔽TCP底层的拆包和粘包问题,只需要传入正确的参数,即可轻松解决“读半包“问题。

发送方使用LengthFieldPrepender给实际内容Content进行编码添加报文头Length字段,接受方使用LengthFieldBasedFrameDecoder进行解码。协议格式如下所示:

+--------+----------+
| Length |  Content |
+--------+----------+
AI生成项目
Length字段:

表示Conent部分的字节数,例如Length值为100,那么意味着Conent部分占用的字节数就是100。

Length字段本身是个整数,也要占用字节,一般会使用固定的字节数表示。例如我们指定使用2个字节(有符号)表示length,那么可以表示的最大值为32767(约等于32K),也就是说,Content部分占用的字节数,最大不能超过32767。当然,Length字段存储的是Content字段的真实长度。

Content字段:

是我们要处理的真实二进制数据。 在发送Content内容之前,首先需要获取其真实长度,添加在内容二进制流之前,然后再发送。Length占用的字节数+Content占用的字节数,就是我们总共要发送的字节。

事实上,我们可以把Length部分看做报文头,报文头包含了解析报文体(Content字段)的相关元数据,例如Length报文头表示的元数据就是Content部分占用的字节数。当然,LengthFieldBasedFrameDecoder并没有限制我们只能添加Length报文头,我们可以在Length字段前或后,加上一些其他的报文头,此时协议格式如下所示:

  +---------+--------+----------+----------+
  |........ | Length |  ....... |  Content |
  +---------+--------+----------+----------+
AI生成项目
不过对于LengthFieldBasedFrameDecoder而言,其关心的只是Length字段。因此当我们在构造一个LengthFieldBasedFrameDecoder时,最主要的就是告诉其如何处理Length字段。

2 、LengthFieldPrepender参数详解
LengthFieldPrepender提供了多个构造方法,最终调用的都是:

 
public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
    this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
}
 
public LengthFieldPrepender(
            ByteOrder byteOrder, int lengthFieldLength,
            int lengthAdjustment, boolean lengthIncludesLengthFieldLength)
 
AI生成项目
其中:

byteOrder:表示Length字段本身占用的字节数使用的是大端还是小端编码

lengthFieldLength:表示Length字段本身占用的字节数,只可以指定 1, 2, 3, 4, 或 8

lengthAdjustment:表示Length字段调整值

lengthIncludesLengthFieldLength:表示Length字段本身占用的字节数是否包含在Length字段表示的值中。

例如:对于以下包含12个字节的报文

   +----------------+
   | "HELLO, WORLD" |
   +----------------+
AI生成项目
假设我们指定Length字段占用2个字节,lengthIncludesLengthFieldLength指定为false,即不包含本身占用的字节,那么Length字段的值为0x000C(即12)。

+--------+----------------+
+ 0x000C | "HELLO, WORLD" |
+--------+----------------+
AI生成项目
如果我们指定lengthIncludesLengthFieldLength指定为true,那么Length字段的值为:0x000E(即14)=Length(2)+Content字段(12)

+--------+----------------+
+ 0x000E | "HELLO, WORLD" |
+--------+----------------+
AI生成项目
关于lengthAdjustment字段的含义,参见下面的LengthFieldBasedFrameDecoder。

LengthFieldPrepender尤其值得说明的一点是,其提供了实现零拷贝的另一种思路(实际上编码过程,是零拷贝的一个重要应用场景)。

在Netty中我们可以使用ByteBufAllocator.directBuffer()创建直接缓冲区实例,从而避免数据从堆内存(用户空间)向直接内存(内核空间)的拷贝,这是系统层面的零拷贝;

也可以使用CompositeByteBuf把两个ByteBuf合并在一起,例如一个存放报文头,另一个存放报文体。而不是创建一个更大的ByteBuf,把两个小ByteBuf合并在一起,这是应用层面的零拷贝。

而LengthFieldPrepender,由于需要在原来的二进制数据之前添加一个Length字段,因此就需要对二者进行合并发送。但是LengthFieldPrepender并没有采用CompositeByteBuf,其编码过程如下:

protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        //1 获得Length字段的值:真实数据可读字节数+Length字段调整值
       int length = msg.readableBytes() + lengthAdjustment;
        if (lengthIncludesLengthFieldLength) {
            length += lengthFieldLength;
        }
        ...
        
        //2 根据lengthFieldLength指定的值(1、2、3、4、8),创建一个ByteBuffer实例,写入length的值,
        //并添加到List类型的out变量中
        switch (lengthFieldLength) {
        case 1:
            if (length >= 256) {
                throw new IllegalArgumentException(
                        "length does not fit into a byte: " + length);
            }
      &

### UDP协议中使用 `LengthFieldBasedFrameDecoder` 的方法 在UDP协议中,虽然其本身是面向无连接的、基于数据报的传输方式,不会像TCP那样出现拆包问题。然而,在某些特定场景下(如多个消息被封装在一个UDP数据报中),仍然可能需要处理类似的问题。此时可以借助 Netty 提供的 `LengthFieldBasedFrameDecoder` 来解析具有长度字段的消息格式。 #### 数据帧结构要求 `LengthFieldBasedFrameDecoder` 适用于自定义协议中含长度字段的情况,通过该字段确定完整消息的长度,从而正确地将消息从数据流中分割出来。这种机制非常适合用于处理封装了多条消息的UDP数据报[^2]。 假设每个UDP数据报中含一条或多条固定格式的消息: ``` +----------------+------------------+ | 长度字段 (4字节)| 实际消息内容 | +----------------+------------------+ ``` 其中,长度字段表示整个消息的长度(括长度字段自身)。 #### 配置示例 为了正确解析上述结构的消息,可以在Netty的 `ChannelPipeline` 中添加 `LengthFieldBasedFrameDecoder`,配置如下参数: - `maxFrameLength`:设置最大允许的数据帧长度。 - `lengthFieldOffset`:指定长度字段在数据帧中的起始偏移量。 - `lengthFieldLength`:指定长度字段的字节数。 - `lengthAdjustment`:调整长度字段值以获取实际消息体长度。 - `initialBytesToStrip`:解码后跳过的前导字节数(通常为长度字段的大小)。 ```java int maxFrameLength = 1024; int lengthFieldOffset = 0; // 长度字段位于数据帧起始位置 int lengthFieldLength = 4; // 长度字段占4个字节 int lengthAdjustment = 0; // 长度字段直接表示整个数据帧的长度 int initialBytesToStrip = 4; // 跳过前4个字节(即长度字段) pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder( maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip )); ``` #### 处理逻辑说明 当UDP数据报到达时,`LengthFieldBasedFrameDecoder` 会首先读取长度字段,判断当前数据是否完整。如果当前数据不足,则等待后续数据到达;如果数据完整,则提取出对应长度的消息,并跳过指定的前导字节(如长度字段),将有效数据传递给后续处理器[^3]。 这种方式可以很好地应对UDP数据报中含多条消息或部分消息的情况,确保每条消息都能被正确解析。 #### 注意事项 - **长度字段需准确**:发送方必须严格按照协议格式填写长度字段,否则可能导致解析失败。 - **数据完整性依赖应用层**:由于UDP本身不保证数据的可靠传输,因此仍需在应用层进行校验和重传等处理。 - **合理设置 `maxFrameLength`**:避免因接收超大帧而导致内存溢出等问题。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广东数字化转型

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值