当前位置: 首页 > article >正文

【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并转换字节序

http://www.kler.cn/a/611498.html

相关文章:

  • Containerd+Kubernetes搭建k8s集群
  • Svelte 深度理解
  • HTML5 SVG:图形绘制的现代标准
  • 告别照片管理难题!PowerPhotos 重塑 Mac 照片管理体验
  • 网络运维学习笔记(DeepSeek优化版) 024 HCIP-Datacom OSPF域内路由计算
  • httpx.AsyncClient()的stream方法设置timeout超时
  • Python:gevent综合案例,进程线程协程对比
  • Go听课笔记
  • AIMB-ASMB-788B(PPC-MB-620B)RAID驱动安装(笔记版)
  • 数据联邦技术与工具:构建实时数据访问的架构实践
  • MySQL-5.7.37安装配置(Windows)
  • 【Godot】导出为安卓安装包
  • 基于SpringBoot的实现的客户关系管理系统(CRM)(源码+数据库)
  • JPA、Hibernate、 Spring Data JPA 以及Mybatis的关系(Java)
  • ctfshow做题笔记—栈溢出—pwn73、pwn74
  • AR沙盘模型制作技术解析,赋能企业展厅创新
  • 线性模型与非线性扩展
  • 管道(Linux)
  • SpringCould微服务架构之Docker(2)
  • 使用Ollama(自定义安装位置)与RagFlow构建本地知识库