【C++网络编程】第8篇:协议设计与序列化(Protobuf、FlatBuffers)
前置
跨平台C++包管理利器vcpkg完全指南
一、为什么需要自定义协议?
1. 常见问题
- 粘包/半包:TCP是流式协议,数据可能被拆分成多个包或合并。
- 数据校验:传输过程中可能发生比特错误或篡改。
- 跨平台兼容性:不同系统对数据类型(如整型字节序)的处理不同。
2. 解决方案
- 定长消息:每个消息固定长度(简单但不够灵活)。
- 分隔符:用特殊字符(如\r\n)分割消息(需转义处理)。
- 长度前缀:在消息头声明数据长度(推荐方案)。
二、设计二进制协议(自定义)
1. 协议格式示例
| 魔数(4B) | 版本(1B) | 类型(1B) | 长度(4B) | 数据(N B) | CRC32(4B) |
字段说明:
- 魔数:固定值(如0xDEADBEEF),用于识别协议起始。
- 版本:协议版本号,支持向后兼容。
- 类型:消息类型(如请求、响应、心跳)。
- 长度:数据部分的字节数。
- 数据:有效载荷(如序列化的JSON或二进制数据)。
- CRC32:校验和,验证数据完整性。
2. C++示例
#include <cstdint>
#include <vector>
#include <cstring>
#include <zlib.h>
#include <iostream>
#include <stdexcept>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
// 平台字节序处理(示例为小端模式)
#if defined(_MSC_VER)
# define htonl(x) _byteswap_ulong(x)
#elif defined(__GNUC__)
# define htonl(x) __builtin_bswap32(x)
#endif
// 严格内存对齐的协议头
#pragma pack(push, 1)
struct PacketHeader {
uint32_t magic; // 固定魔数 0xDEADBEEF
uint8_t version; // 协议版本
uint8_t type; // 数据类型标识
uint32_t length; // 载荷长度(网络字节序)
PacketHeader(uint8_t t = 1, uint32_t len = 0)
: magic(htonl(0xDEADBEEF)), // 存储为网络字节序
version(1),
type(t),
length(htonl(len)) {
}
};
#pragma pack(pop)
// 编码函数:生成带校验的数据包
std::vector<uint8_t> EncodePacket(uint8_t type, const std::vector<uint8_t>& payload) {
if (payload.size() > 0xFFFFFFFF) {
throw std::invalid_argument("Payload too large");
}
// 构造协议头
PacketHeader header(type, static_cast<uint32_t>(payload.size()));
// 计算CRC32(头+载荷)
std::vector<uint8_t> tempBuffer;
tempBuffer.reserve(sizeof(header) + payload.size());
tempBuffer.insert(tempBuffer.end(),
reinterpret_cast<uint8_t*>(&header),
reinterpret_cast<uint8_t*>(&header) + sizeof(header));
tempBuffer.insert(tempBuffer.end(), payload.begin(), payload.end());
uLong crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, tempBuffer.data(), tempBuffer.size());
uint32_t netCrc = htonl(crc); // CRC也转为网络字节序
// 组装完整包
std::vector<uint8_t> packet;
packet.insert(packet.end(), tempBuffer.begin(), tempBuffer.end());
packet.insert(packet.end(),
reinterpret_cast<uint8_t*>(&netCrc),
reinterpret_cast<uint8_t*>(&netCrc) + sizeof(netCrc));
return packet;
}
// 解码函数:验证并提取数据
bool DecodePacket(const std::vector<uint8_t>& packet,
uint8_t& type,
std::vector<uint8_t>& payload) {
// 基础长度检查
const size_t minSize = sizeof(PacketHeader) + sizeof(uint32_t);
if (packet.size() < minSize) return false;
// 解析协议头
PacketHeader header;
memcpy(&header, packet.data(), sizeof(header));
header.magic = ntohl(header.magic); // 转换为主机字节序
header.length = ntohl(header.length);
// 魔数和版本校验
if (header.magic != 0xDEADBEEF || header.version != 1) {
return false;
}
// 载荷长度校验
const size_t expectedSize = sizeof(header) + header.length + sizeof(uint32_t);
if (packet.size() != expectedSize) {
return false;
}
// 提取CRC并转换字节序