这个是比较早, 09年4月份的事情了。整理文档翻出来,觉得还有点意思.
当时CLIENT-SERVER的通讯封包格式有两种方案
a. 以7E为开头和结尾, PAYLOAD中所有7E的字节, 都在其后扩展一个BYTE, 写为7E, 7D, (称为转义). 封包中不带CHECKSUM, CRC等校验用的字段
b. 以7E为开头和结尾, 带一个CHECKSUM字段, PAYLOAD中不进行7E->7E 7D的转义.
几个同事就这个通信封包格式, 采用方案一或方案二, 开会激烈讨论了个把小时。
我原先反对转义方案的出发点比较模糊, 只是觉得原先转义的方案"不优雅"; 后来才想清楚了不优雅的"本质"在哪里.
所有的代码, 可以在抽象意义上分作两大块, 两者的着重点是不同的.
(1) 正常运行的代码. 首要追求高效性,
这个"高效性"如果从逻辑的角度来解释, 那么一方面是"高效"地对正确的数据执行正确的算法(方法/策略), 另一方面是"高效"地找出异常, 然后丢给异常处理代码去处理.
(2) 处理异常的代码. 首要追求健壮性.
就是程序必须能从异常中自我恢复. 由于代码多数时间跑的是"正常"逻辑, 少数情况下才不得不处理"异常", 所以"异常"处理的代码中, 首要任务是健壮, 跑不死, 而高效性则是次要的.
那么回到转义的策略上来看,原先的7E -> 7E 7D, 使得装包和拆包的时候, 时间上都必须挨字节扫描过去, 空间上必须另开一块内存, 这些"不优雅"的工作是为了应对网络传输时包数据丢失. 包数据丢失是一个异常情况,而转义策略本质上就是不论好包坏包,一棍子打死, 统统要经过转义算法. 用上面的观点解释, 即"为了异常情况下的健壮性,牺牲了正常情况下的高效性".
而用Header + Length + CheckSum + Payload + Tailer的做法, 逻辑上是这样的
这是在上面"正常->高效性 & 异常->健壮性"指导思想下的做法. 那么现在就剩最后一个问题, 计算checksum和转义的工作相比, 哪一个更快? 如果转义处理的效率, 比checksum更高,那么上面的假设就不成立了.
所以我做了个实验, 代码如下
上面这段代码,在SAMSUNG 2442 400MHz的CPU, WM 6.1系统上运行结果是
copy 1024 bytes * 100K times, use 11677 ms
check sum 1024 bytes *100K times, use 7504 ms
所以, 一个正确的数据包, 经过CHECKSUM计算的时间, 比其经过转义计算的时间要快得多, 仅为其64%. 这是手机上的情况, 服务器上的百分比不太清楚是什么样,但至少有一点是肯定的,就是用CHECKSUM的方案比用转义的方案,在正常逻辑情况下速度更快、内存开销更少。当服务器同时处理十万数量级网络数据包的时候, 性能提升还是比较可观的。
这篇文章的重点不在于哪个方案更严谨,或者上面的逻辑对不对,而是在于这么一个思想:
(1) 正常运行的代码. 首要追求高效性,
(2) 处理异常的代码. 首要追求健壮性.