3、固定长度方案:每个消息固定长度,不足补位,接收方按长度截取

好,咱们接着聊粘包拆包。前面讲了为什么会出现这个问题,也说了最粗暴的按行分割。今天这个方案,我个人觉得是「最省脑子」的一种——固定长度。

说白了,就是通信双方约定好:每条消息就这么长,比如 1024 字节。你发过来的数据,不够 1024 字节?我给你补上。超过 1024 字节?对不起,你得拆成多条发。

接收方这边更简单:每次就从缓冲区里读 1024 个字节,不多不少。读出来就是一条完整的消息。

3.1 为什么会有这种方案?

你想想看,粘包拆包的本质是什么?是 TCP 的流式传输没有边界。那如果我们强行给每个消息画一条边界呢?

固定长度就是最直接的边界划定方式。我在项目中遇到过一些老旧的嵌入式系统,CPU 性能很弱,内存也小。那种场景下,你让它去做复杂的协议解析、查找分隔符,它扛不住。固定长度方案就成了救命稻草——无脑截取就行,计算量几乎为零。

3.2 具体怎么实现?

实现思路其实就三步:

  1. 约定长度:双方协商好一个固定值,比如 128 字节、512 字节、1024 字节。
  2. 发送时补位:如果消息体不足约定长度,用特定字符(通常是 '\0' 或空格)填充到约定长度。
  3. 接收时截取:每次从接收缓冲区读取约定长度的字节,直接作为一条完整消息处理。

来看个简单的 Java 实现:

// 约定消息固定长度为 128 字节
private static final int MESSAGE_LENGTH = 128;
private static final byte PADDING_CHAR = (byte) '\0';

// 发送端:补位到固定长度
public byte[] encode(String message) {
    byte[] data = message.getBytes(StandardCharsets.UTF_8);
    byte[] frame = new byte[MESSAGE_LENGTH];
    
    // 复制有效数据
    int copyLength = Math.min(data.length, MESSAGE_LENGTH);
    System.arraycopy(data, 0, frame, 0, copyLength);
    
    // 不足部分自动补 '\0'(Java 数组默认初始化为 0)
    return frame;
}

// 接收端:直接按长度截取
public String decode(byte[] buffer, int offset) {
    // 从 offset 位置读取 MESSAGE_LENGTH 个字节
    byte[] frame = new byte[MESSAGE_LENGTH];
    System.arraycopy(buffer, offset, frame, 0, MESSAGE_LENGTH);
    
    // 去除尾部补位字符
    int validLength = 0;
    for (int i = 0; i < MESSAGE_LENGTH; i++) {
        if (frame[i] == PADDING_CHAR) {
            break;
        }
        validLength++;
    }
    
    return new String(frame, 0, validLength, StandardCharsets.UTF_8);
}
💡 我的小技巧:补位字符的选择有讲究。用 '\0' 最省事,但如果你的消息内容本身就可能包含 '\0',那就得换个字符。我习惯用空格(0x20),因为大多数文本协议里,消息末尾的空格是可以安全去除的。

3.3 这个方案的优缺点

优点 缺点
实现极其简单,代码量少 带宽浪费严重,短消息也要占满固定长度
解析效率高,无状态处理 消息长度受限,不能超过约定值
不存在粘包拆包问题 灵活性差,协议变更需要两端同时改
适合硬件资源受限的场景 长消息需要应用层自行分片和重组

3.4 适用场景分析

说实话,现在大部分互联网后端开发已经很少用纯固定长度方案了。但有几个场景,它依然是首选:

  • 嵌入式设备通信:传感器数据、控制指令,每条消息结构固定,长度也固定。
  • 金融交易系统:某些古老的交易协议,比如 FIX 协议的某些变种,就用了固定长度字段。
  • 音视频传输:RTP 包的头部就是固定长度的,12 字节,不多不少。
  • 日志采集:每条日志截取固定长度,虽然会丢失信息,但性能极高。
⚠️ 避坑指南:我曾经在一个物联网项目里吃过亏。当时设备上报的数据包长度是 256 字节,我觉得够用了。结果后来业务扩展,需要上报 GPS 轨迹点,一条轨迹有几十个坐标点,256 字节根本装不下。最后不得不升级固件,改协议。所以约定固定长度时,一定要给未来留足余量。

3.5 核心逻辑流程图

下面这张图,把固定长度方案的核心流程画清楚了:

固定长度方案核心流程 发送端 接收端 1. 获取原始消息 例如:"Hello" (5字节) 2. 补位到固定长度 补 '\0' 到 128 字节 3. 发送完整帧 直接写入 Socket 1. 读取固定长度 从缓冲区读 128 字节 2. 去除补位字符 找到第一个 '\0' 截断 3. 还原原始消息 得到 "Hello" TCP 传输 关键点 发送端和接收端必须约定相同的固定长度值 补位字符的选择要避开消息内容中可能出现的字符

3.6 实际项目中的变种

纯固定长度方案太死板,所以实际项目中经常看到它的变种:

  • 固定头部 + 可变体:头部固定 8 字节,里面存了消息体的长度。这其实已经算下一个章节要讲的「长度域方案」了。
  • 固定长度字段:消息体本身是变长的,但里面的每个字段是固定长度的。比如用户名字段固定 32 字节,密码字段固定 64 字节。
  • 固定长度 + 版本号:在固定长度的末尾加一个版本号字段,方便后续协议升级。

总结一下:固定长度方案,简单、可靠、高效。但它牺牲了灵活性。如果你的消息长度变化不大,或者你正在做一个资源受限的系统,它是个好选择。反之,如果消息长度差异很大,或者业务变化频繁,我建议你看看后面几种方案。

嗯,固定长度方案就聊到这儿。它虽然简单,但背后的思想很重要——用空间换确定性。很多时候,简单的方案反而是最可靠的。

可可精品资料库