CppGuide社区 CppGuide社区
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章高频C++11重难点知识解析

  • 第2章Linux GDB高级调试指南

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

  • 第5章网络通信故障排查常用命令

  • 第6章高性能网络通信协议设计精要

    • 6.1 TCP 协议是流式协议
    • 6.2 如何解决粘包问题
    • 6.3 解包与处理
    • 6.4 从 struct 到 TLV——协议的演化历史
    • 6.5 整型数值的压缩
    • 6.6 通信协议设计时的注意事项
      • 6.7 包分片
      • 6.8 跨语言之间的网络通信协议识别与解析
      • 6.9 xml 与 json 格式协议
      • 6.10 一个自定义协议示例
      • 6.11 http 协议
      • 6.12 SMTP、POP3 协议与邮件客户端
      • 6.13 WebSocket 协议
    • 第7章高性能服务结构设计

    • 第8章Redis 网络通信模块源码分析

    • 第9章后端服务重要模块设计探索

    • C++后端开发进阶
    • 第6章高性能网络通信协议设计精要
    zhangxf
    2023-04-05
    目录

    6.6 通信协议设计时的注意事项

    通过前面的章节的讨论,相信读者应该对协议设计有一定的了解了。本节我们来讨论一下协议设计时的一些注意事项。

    # 6.6.1 字节对齐

    留心的读者一定注意到,前面讨论的协议示例中:

    #pragma pack(push, 1)
    struct userinfo
    {
        //版本号
        short   version;
        //命令号
        int32_t cmd;
        //用户性别
        char    gender;
        //用户昵称
        char	name[8];
        //用户年龄
        int32_t	age;
    };
    #pragma pack(pop)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    有一组成对的 #pragma XX 指令,其中 #pragma pack(push, n),是告诉编译器接下来的所有结构体(这里就是 userinfo 协议)的每一个字段按 n 个字节对齐,这里 n = 1,按一个字节对齐,即去除任何 padding 字节。这样做的目的是为了内存更加紧凑,节省存储空间。

    我们不再需要这个对齐功能后,应该使用 #pragma pack(pop) 让编译器恢复之前的对齐方式。

    注意:#pragma pack(push, n) 与 #pragma pack(pop) 一定要成对使用,如果你漏掉其中任何一个,编译出来的代码可能会出现很多奇怪的运行结果。
    
    1

    # 6.6.2 显式指定整型字段的长度

    对于一个 int 型字段,在作为协议传输时,我们应该显式地指定该类型的长度,也就是说,你应该使用 int32_t、int64_t 这样的类型来代替 int、long。之所以这么做,是因为在不同字长的机器上,对于默认的 int 和 long 的长度可能不一样,例如 long 型,在 32 位操作系统上其长度是 4 个字节,而在 64 位机器上其长度是 8 个字节。如果不显式指定这种整形的长度,可能因为不同机器字长不同,导致协议解析出错或者产生错误的结果。

    # 6.6.3 涉及到浮点数要考虑精度问题,建议放大成整数或者使用字符串去传输

    由于计算机表示浮点数存在精度取舍不准确的问题,例如对于 1.000000,有的计算机可能会得到 0.999999,在某些应用中,如果这个浮点数的业务单位比较大(如表示金额,单位为亿),就会造成很大的影响。因此为了避免不同的机器解析得到不同的结果,建议在网络传输时将浮点数值放大相应的倍数变成整数或者转换为字符串来进行传输。

    # 6.6.4 大小端编码问题

    在第四章我们已经详细地介绍大小端的问题(即主机字节序和网络字节序),在设计协议格式时,如果协议中存在整型字段,建议使用同一个字节序。通常的做法是在进行网络传输时将所有的整型转换为网络字节序(大端编码,Big Endian),避免不同的机器因为大小端问题解析得到不同的整型值。

    当然,不一定非要转换为网络字节序,如果明确的知道通信的双方使用的是相同的字节序,则也可以不转换。

    # 6.6.5 协议与自动升级功能

    对于一个商业的产品,发布出去的客户端一般通过客户端的自动升级功能去获得更新(IOS App 除外,苹果公司要求所有的 App 必须在其 App Store 上更新新版本,禁止热更新)。在客户端与服务器通信的所有协议格式中,自动升级协议是最重要的一个,无论版本如何迭代,一定要保证自动升级协议的新旧兼容,这样做有如下原因:

    • 如果新的服务器不能兼容旧客户端中的自动升级协议,那么旧的客户端用户将无法升级成新的版本了,这样的产品相当于把自己给“阉割”了。对于不少产品,不通过自动升级而让众多用户去官网下载新的版本是一件很难做到的事情,这种决策可能会导致大量用户流失;
    • 退一步讲,对于一些测试不完善,或者处于快速迭代中的产品,只要保证自动升级功能正常,旧版本任何 bug 和瑕疵都可以通过升级新版本解决。这对于一些想投放市场试水,但又可能设计不充分的产品尤其重要。

    顺便提一下,一般自动升级功能是根据当前版本的版本号与服务器端新版本的版本号进行比较,如果二者之间存在一个大版本号的差别(如1.0.0 与 2.0.0),即有重大功能更新,则应该强制客户端更新下载最新版本;如果只是一个小版本号的更新(如 1.0.0 与 1.1.0),则可以让用户选择是否更新。当然,如果是新版本修正了前一个版本中严重影响使用的 bug,也应当强制用户更新。

    上次更新: 2025/05/19, 16:52:22
    6.5 整型数值的压缩
    6.7 包分片

    ← 6.5 整型数值的压缩 6.7 包分片→

    最近更新
    01
    第二章 关键字static及其不同用法
    03-27
    02
    第一章 auto与类型推导
    03-27
    03
    第四章 Lambda函数
    03-27
    更多文章>
    Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式