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

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

相关文章:

  • 流式ETL配置指南:从MySQL到Elasticsearch的实时数据同步
  • 【设计模式】工厂模式
  • 信息学奥赛一本通 1514:【例 2】最大半连通子图 | 洛谷 P2272 [ZJOI2007] 最大半连通子图
  • vue watch数据监听
  • R语言——字符串
  • RTSP/Onvif安防监控平台EasyNVR抓包命令tcpdump使用不了,该如何解决?
  • 模型搭建与复现
  • 【Linux网络-多路转接select】
  • Active Directory (AD): 企业网络用户管理的重要性及 AD 迁移方法
  • UNIX网络编程笔记:TCP、UDP、SCTP编程的区别
  • 解决 MySQL 的 sql_mode 中包含 only_full_group_by模式导致group by SQL报错
  • PHP eval 长度限制绕过与 Webshell 获取
  • 穿透Session 0隔离
  • 【每日算法】Day 6-1:哈希表从入门到实战——高频算法题(C++实现)
  • 网络安全基础:五类安全服务、八种安全机制与OSI七层模型的全面解析
  • HTML——什么是块级元素,什么是内联元素,有何区别
  • 使用Django创建项目及介绍
  • OBS虚拟背景深度解析:无需绿幕也能打造专业教学视频(附插件对比)
  • 小蓝的括号串(栈,dfs)
  • 电气、电子信息与通信工程的探索与应用
  • 做影视网站怎么/北京网站快速排名优化
  • 国际外贸网站推广/青岛seo网站排名
  • 自助网站建设方法/留号码的广告网站
  • 哪些网站是jsp做的/企业网站网页设计
  • 网站程序怎么做/营销策划公司收费明细
  • 域名续费后网站打不开/长沙百度地图