3
0
0

Netty:LengthFieldBasedFrameDecoder 五个参数与 lengthAdjustment 计算原理

2026-11-01
2026-11-01
文章摘要
|

一、先理解长度字段的三个概念

使用 LengthFieldBasedFrameDecoder
时,最容易混淆的是:长度字段本身占多少字节,以及长度字段里面存储的数值是多少。

例如协议:

[Magic 2字节][BodyLength 2字节][Body 10字节]

概念

值

含义

lengthFieldOffset

2

BodyLength 从整条帧的索引 2 开始

lengthFieldLength

2

BodyLength 字段自身占 2 字节

lengthFieldValue

10

BodyLength 字段里面存储的数值,表示正文长度

因此,BodyLength 字段本身占 2 字节,但里面存储的数值是
10。这是两个不同的概念。

字段名称不一定能说明它存储的内容。协议可以定义 BodyLength
只记录正文长度,也可以定义 TotalLength
记录整条消息的总长度,必须以协议约定为准。

二、五个参数分别做什么?

new LengthFieldBasedFrameDecoder(
    maxFrameLength,
    lengthFieldOffset,
    lengthFieldLength,
    lengthAdjustment,
    initialBytesToStrip
);

参数

作用

需要回答的问题

maxFrameLength

限制完整帧的最大长度

一条完整消息最大允许多少字节?

lengthFieldOffset

长度字段相对帧起始位置的偏移量

长度字段从第几个字节开始?

lengthFieldLength

长度字段自身占用的字节数

长度字段本身有几个字节?

lengthAdjustment

修正完整帧长度的计算结果

长度字段记录的范围与完整帧长度之间差多少?

initialBytesToStrip

完整帧提取后,跳过帧开头的字节数

下一个 Handler 需要保留消息头,还是只需要正文?

1. maxFrameLength

它限制的是完整帧的最大长度,包含消息头和正文,不只是正文。

例如:

new LengthFieldBasedFrameDecoder(
    1024,  // 完整帧最大 1024 字节
    2,
    2,
    0,
    4
);

如果消息头是 4 字节,那么正文最大可用长度还要扣除消息头所占空间。

2. lengthFieldOffset

它表示长度字段从帧的哪个字节开始,索引从 0 开始。

[Magic 2字节][BodyLength 2字节][Body]
  0       1    2          3
               ↑
          offset = 2

所以 lengthFieldOffset = 2。offset
只是定位长度字段,不代表跳过这些字节。是否跳过消息头由
initialBytesToStrip 决定。

3. lengthFieldLength

它表示长度字段自身占用多少字节。

例如 [Magic 2字节][BodyLength 2字节][Body],那么
lengthFieldLength = 2。

不要把 lengthFieldLength 和 lengthFieldValue 混淆:

  • lengthFieldLength = 2:字段自身占 2 字节。

  • lengthFieldValue = 10:字段里面存储的数值是 10。

三、lengthAdjustment:如何计算完整帧长度?

这是五个参数中最容易混淆的一个。

Netty 使用的计算公式:

frameLength = lengthFieldEndOffset
              + lengthFieldValue
              + lengthAdjustment

其中:

lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength

合并后:

\(FrameLength=Offset+FieldLength+LengthValue+Adjustment\)

也可以反过来计算:

\(Adjustment=实际完整帧长度-Offset-FieldLength-LengthValue\)

理解 adjustment
的关键:先看长度字段里面的数值统计了哪些字节,再看公式还需要补充或扣除哪些字节。

情况一:长度字段只记录正文长度,后面没有其他字段

协议:

[Magic 2字节][BodyLength 2字节][Body 10字节]

约定 BodyLength 只记录正文长度,所以字段里面的数值是 10。

完整帧长度:

2 + 2 + 10 = 14 字节

代入公式:

frameLength = 2 + 2 + 10 + 0
            = 14

所以 lengthAdjustment = 0。公式已经计算了长度字段之前的 2
字节、长度字段自身的 2 字节,再加上正文长度 10,刚好得到完整帧长度。

情况二:长度字段只记录正文长度,但长度字段后面还有额外字段

协议:

[Magic 2字节][BodyLength 2字节][Version 1字节][Body 10字节]

约定 BodyLength 仍然只记录 Body,所以字段里面的数值是 10。

但完整帧还包含一个没有被 BodyLength 统计的 Version 字段。

完整帧长度:

2 + 2 + 1 + 10 = 15 字节

代入公式:

frameLength = 2 + 2 + 10 + 1
            = 15

所以 lengthAdjustment = 1。这里的 +1
是补上长度字段之后、但没有被长度值统计的 Version 字段。

情况三:长度字段记录 Version + Body

协议仍然是:

[Magic 2字节][Length 2字节][Version 1字节][Body 10字节]

但这次 Length 存储的是:

Version 1字节 + Body 10字节 = 11

完整帧长度:

2 + 2 + 11 = 15 字节

代入公式:

frameLength = 2 + 2 + 11 + 0
            = 15

所以 lengthAdjustment = 0。因为长度值 11 已经包含了 Version 和
Body,不需要再额外补 1 字节。

情况四:长度字段记录整条消息的总长度

协议:

[Magic 2字节][TotalLength 2字节][Body 10字节]

约定 TotalLength 存储整条消息的总长度,因此字段里面的数值是 14。

注意:这是另一种协议设计。它与前面 BodyLength = 10
的协议结构相同,但长度字段存储的含义不同。

Netty 公式中的前两项已经计算了 Magic 2 字节和 TotalLength 字段自身 2
字节。而 TotalLength = 14 已经包含这 4
字节。如果直接相加,就重复计算了消息头。

所以需要减去 4:

frameLength = 2 + 2 + 14 - 4
            = 14

因此 lengthAdjustment = -4。

-4 的原因:Length 字段存储的 14 已经包含前面的 4
字节消息头,而公式又计算了一次,所以要减去 4。

四种情况对比

协议约定

长度字段存储值

lengthAdjustment

只记录 Body

10

0

只记录 Body,后面还有未统计的 Version 1 字节

10

+1

记录 Version + Body

11

0

记录整条消息总长度

14

-4

不要死记“正文长度就一定是
0”或“总长度就一定是负数”。应使用反推公式,根据实际协议字段计算:

adjustment = 实际完整帧长度
             - lengthFieldOffset
             - lengthFieldLength
             - lengthFieldValue

四、initialBytesToStrip:完整帧提取后跳过多少字节?

这个参数与 lengthAdjustment 不同。

  • lengthAdjustment:参与计算完整帧长度。

  • initialBytesToStrip:完整帧提取出来后,决定从帧开头跳过多少字节,再交给下一个
    Handler。

它不参与帧长度计算,也不会改变消息边界。

继续使用协议:

[Magic 2字节][BodyLength 2字节][Version 1字节][Body 10字节]

完整消息头共 5 字节。

initialBytesToStrip

下一个 Handler 收到的内容

0

Magic + BodyLength + Version + Body,完整帧

4

Version + Body

5

只有 Body

如果下一个 Handler 还需要解析 Magic、长度和 Version,就设置为
0;如果只需要正文,就设置为 5。

例如:

new LengthFieldBasedFrameDecoder(
    1024,
    2,
    2,
    0,
    5
);

这表示:按长度字段完成组帧后,跳过前 5 字节消息头,只把 Body 交给下一个
Handler。

五、根据协议配置参数:完整示例

示例一:Length 只记录 Body 长度

协议:

[Magic 2][BodyLength 2][Body N]

配置:

pipeline.addLast(
    new LengthFieldBasedFrameDecoder(
        1024, // 最大完整帧长度
        2,    // 长度字段从索引 2 开始
        2,    // 长度字段占 2 字节
        0,    // BodyLength 只记录正文,后面没有额外字段
        4     // 跳过 Magic 和 BodyLength,只保留 Body
    )
);

示例二:Length 记录整条消息总长度

协议:

[Magic 2][TotalLength 2][Body N]

假设 TotalLength 包含整个消息头和正文。

配置:

pipeline.addLast(
    new LengthFieldBasedFrameDecoder(
        1024,
        2,
        2,
        -4,  // TotalLength 已包含前面 4 字节,扣除重复计算
        4     // 跳过 4 字节消息头,只保留 Body
    )
);

两种协议的物理结构相同,但长度字段的定义不同,因此 lengthAdjustment
不同。

六、常见问题与答案

1. BodyLength 是字段长度吗?

BodyLength 是字段名称。字段自身占多少字节由 lengthFieldLength
表示;字段里面存储的数值由 lengthFieldValue 表示。例如字段占 2
字节,里面存储的正文长度是 10。

2. lengthFieldOffset 是不是跳过消息头的长度?

不是。它只表示长度字段从帧起始位置的哪个索引开始。真正决定输出时跳过多少字节的是
initialBytesToStrip。

3. 为什么 BodyLength = 10 时 adjustment 可以是 0?

例如 [Magic 2][BodyLength 2][Body 10],公式中的 2 + 2 + 10
已经得到完整帧长度 14,不需要调整。

4. 为什么 TotalLength = 14 时 adjustment 是 -4?

因为 TotalLength 的 14 已经包含 Magic 和 Length 共 4
字节,而公式又计算了这 4 字节,所以要减去 4。

5. 如果长度字段记录 Version + Body,adjustment 是多少?

如果协议是 [Magic 2][Length 2][Version 1][Body 10],且 Length =
11,那么公式 2 + 2 + 11 已经得到完整帧长度 15,因此 adjustment = 0。

6. initialBytesToStrip 会影响完整帧长度吗?

不会。它只影响组帧后交给下一个 Handler
的数据内容,不参与完整帧长度计算。

7. maxFrameLength 限制的是正文长度吗?

不是。它限制完整帧长度,包含消息头和正文。

七、重点总结

  1. lengthFieldOffset:长度字段从帧起始位置的哪个字节开始。

  2. lengthFieldLength:长度字段自身占多少字节。

  3. lengthFieldValue:长度字段内部存储的数值,具体含义由协议定义。

  4. lengthAdjustment:修正完整帧长度,计算公式为
    offset + fieldLength + fieldValue + adjustment。

  5. initialBytesToStrip:完整帧提取后,从帧开头跳过多少字节。

  6. maxFrameLength:限制完整帧的最大长度,包括消息头和正文。

  7. BodyLength = 10 与完整帧长度为 14
    并不矛盾:前者是正文长度,后者是消息头加正文的总长度。

  8. TotalLength = 14 时,若长度字段结束位置为 4,则 adjustment 为
    -4,因为长度值已经包含了前面的 4 字节。

  9. 计算参数时先画协议结构,标出字段位置和长度,再确认长度字段统计范围,最后代入公式,不要死记固定数值。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论