05 通信协议设计时的注意事项 通过前面的章节的讨论相信读者应该对协议设计有一定的了解了。本节我们来讨论一下协议设计时的一些注意事项。字节对齐留心的读者一定注意到前面讨论的协议示例中#pragma pack(push, 1) struct userinfo { //版本号 short version; //命令号 int32_t cmd; //用户性别 char gender; //用户昵称 char name[8]; //用户年龄 int32_t age; }; #pragma pack(pop)有一组成对的#pragma XX指令其中 #pragma pack(push, n)是告诉编译器接下来的所有结构体这里就是 userinfo 协议的每一个字段按 n 个字节对齐这里 n 1按一个字节对齐即去除任何 padding 字节。这样做的目的是为了内存更加紧凑节省存储空间。我们不再需要这个对齐功能后应该使用#pragma pack(pop)让编译器恢复默认的对齐方式。注意#pragma pack(push, n) 与 #pragma pack(pop) 一定要成对使用如果你漏掉其中任何一个编译出来的代码可能会出现很多奇怪的运行结果。显式指定整型字段的长度对于一个 int 型字段在作为协议传输时我们应该显式地指定该类型的长度也就是说你应该使用 int32_t、int64_t 这样的类型来代替 int、long。之所以这么做的原因是对于不同字长的机器对于默认的 int 和 long 的长度可能不一样例如 long 型在 32 位操作系统上其长度是 4 个字节而在 64 位机器上其长度是 8 个字节。如果不显式指定这种整形的长度可能因为不同机器字长不同导致协议解析出错或者产生错误的结果。涉及到浮点数要考虑精度问题建议放大成整数或者使用字符串去传输由于计算机表示浮点数存在精度取舍不准确的问题例如对于 1.000000有的计算机可能会得到 0.999999在某些应用中如果这个浮点数的业务单位比较大如表示金额单位为亿就会造成很大的影响。因此为了避免不同的机器解析得到不同的结果建议在网络传输时将浮点数值放大相应的倍数变成整数或者转换为字符串来进行传输。大小端编码问题在第四章我们已经详细地介绍大小端的问题即主机字节序和网络字节序在设计协议格式时如果协议中存在整型字段建议使用同一个字节序。通常的做法是在进行网络传输时将所有的整型转换为网络字节序大段编码Big Endian避免不同的机器因为大小端问题解析得到不同的整型值。当然不一定非要转换为网络字节序如果明确的知道通信的双方使用的是相同的字节序则也可以不转换。协议的分类根据协议的内容是否是文本格式即人为可读格式我们将协议分为文本协议和二进制协议像 http 协议的包头部分和 FTP 协议等都是典型的文本协议的例子。协议与自动升级功能对于一个商业的产品发布出去的客户端一般通过客户端的自动升级功能去获得更新IOS App 除外苹果公司要求所有的 App 必须在其 App Store 上更新新版本禁止热更新。在客户端与服务器通信的所有协议格式中自动升级协议是最重要的一个无论版本如何迭代一定要保证自动升级协议的新旧兼容这样做有如下原因如果新的服务器不能兼容旧客户端中的自动升级协议那么旧的客户端用户将无法升级成新的版本了这样的产品相当于把自己给“阉割”了。对于不少产品不通过自动升级而让众多用户去官网下载新的版本是一件很难做到的事情这种决策可能会导致大量用户流失退一步讲对于一些测试不完善或者处于快速迭代中的产品只要保证自动升级功能正常旧版本任何 bug 和瑕疵都可以通过升级新版本解决。这对于一些想投放市场试水但又可能设计不充分的产品尤其重要。顺便提一下一般自动升级功能是根据当前版本的版本号与服务器端新版本的版本号进行比较如果二者之间存在一个大版本号的差别如1.0.0 与 2.0.0即有重大功能更新则应该强制客户端更新下载最新版本如果只是一个小版本号的更新如 1.0.0 与 1.1.0则可以让用户选择是否更新。当然如果是新版本修正了前一个版本中严重影响使用的 bug也应答强制用户更新。