C++使用“长度前缀法”解决TCP“粘包 / 拆包”问题

背景

TCP 传输数据时,不是一个包一个包传输,而是流式传输。可以理解为把发送的数据包内容都放到一起,会把数据当成连续的字节流发送,不会保留 "数据包边界"。

例如:

某发送端(客户端)分两次发,如,包1:100字节包2:200字节,接收端(服务端)可能一次收到 300字节(粘包),或分多次收到 50字节+250字节(拆包)。

所以写代码接收时如果直接按固定长度读取,很容易读错数据(比如把包 1 的后半部分和包 2 的前半部分当成一个包)。

解决方法

使用"长度前缀法",可以解决这个问题。

为了让接收端(服务端)知道 "每个数据包有多大",发送端(客户端)会在每个数据包的最开头,先发送一个 quint32 类型的长度前缀(占 4 字节),表示 "后续数据的字节数"。

例如:要发一个 100 字节的数据包,发送端实际发:quint32(100) + 100字节的数据

如下这个接收端(服务端)代码:

cpp 复制代码
if (m_blockSize == 0) {  // 步骤1:还没读取当前数据包的长度前缀
    // 检查当前接收缓冲区中,是否至少有 4 字节(quint32的大小)
    if (m_clientSocket->bytesAvailable() < (int)sizeof(quint32)) {
        return;  // 不够4字节,等待更多数据到达
    }
    in >> m_blockSize;  // 步骤2:读取长度前缀,存入m_blockSize
}

// 步骤3:后续逻辑(检查是否有足够数据来读取m_blockSize长度的内容)
if (m_clientSocket->bytesAvailable() < m_blockSize) {
    return;  // 数据还没到齐,继续等待
}

// 读取数据
QByteArray data = m_clientSocket->read(m_blockSize);    //只读取m_blockSize这么多
m_blockSize = 0;

关键细节:bytesAvailable() < sizeof(quint32)

  • sizeof(quint32) == 4,表示长度前缀占 4 字节。
  • 如果接收缓冲区的可用字节数 不足 4 字节,说明长度前缀还没完全收到(比如只收到 2 字节),此时强行读取会导致:
  • 读取到不完整的数值(比如把 2 字节当 4 字节读,结果完全错误);
  • 甚至触发程序崩溃(流读取越界)。

因此,必须先判断 bytesAvailable() >= 4,确保能读到完整的长度前缀。

通过 "先读长度前缀,再读数据内容" 的策略,解决 TCP 流式传输的粘包 / 拆包问题:

  1. 先等待至少 4 字节(长度前缀)到达,读取数据包的总长度 m_blockSize;
  1. 再等待至少 m_blockSize 字节到达,读取实际的数据内容;
  1. 确保每次处理的都是完整的数据包,避免解析错误。

如果省略 bytesAvailable() < sizeof(quint32) 的判断,当长度前缀未完全到达时就强行读取,会导致数据解析错误或程序崩溃。

附录

发送端(客户端)对应的代码:

cpp 复制代码
// 发送文件信息
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_12);
m_fileInfo.serialize(stream);

// 先发送数据大小,再发送数据内容
quint32 blockSize = data.size();
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_12);
out << blockSize << data;

m_socket->write(block);

从中可知,在发送端(客户端)时,发送每个包头部都是数据大小,然后才是内容。通过这样的方式,让接收端(服务端)处理,接收数据。

相关推荐
是逍遥子没错13 小时前
一个斜杠,击穿整座网关:API网关路径前缀绕过认证实战
运维·服务器·web安全·网络安全·渗透测试·系统安全
cpp_learners13 小时前
C++ 实现责任链模式(Chain of Responsibility):从一堆 if-else 到可插拔的处理管道
开发语言·c++·责任链模式
郝学胜_神的一滴14 小时前
C++11 工程级应用 12:编译期类型魔法,干掉重复与臃肿的代码
c++·visual studio
llilian_1614 小时前
时间统一系统 高精度时统设备选购避坑指南 授时系统
大数据·网络·人工智能·功能测试·单片机·嵌入式硬件·51单片机
Fluxart.ai14 小时前
GPT Image 2.5 有使用次数限制吗?额度、并发、频率限制说明
服务器·前端·gpt
其实防守也摸鱼14 小时前
每天一个知识点——RCE漏洞
运维·服务器·数据库·windows·安全·github·漏洞
2501_9378609414 小时前
网络核心|OSI七层、TCP/IP五层模型,封装和分用详解
网络·网络协议·tcp/ip
llilian_1614 小时前
标准时间间隔发生器应用解决方案 脉冲发生器 时间测量仪
大数据·网络·人工智能·功能测试·单片机·嵌入式硬件·51单片机
AKA__Zas15 小时前
网络速通之网络通信
网络
ao-weilai15 小时前
Linux网络编程:网络知识基础
linux·网络·php