在进行 <<协议设计>>系列之前,先写点零碎的知识,做些铺垫.
Google的libprotobuf,已经很强大了,开始接触的时候还是2.1版本,现在已经到了2.2了,新版最大的变化就是添加了libprotobuf-lite!,是libprotobuf的精简版,更加的轻量级,非常适合我的口味。
我比较推崇这个数据交换引擎,平时也在自己的代码里面有过应用,比如做简单的IM,文件传输等,都极为方便,看重的几点好处如下(手册中描述的好处就省了):
1.提供接口描述语言(IDL),无论是做客户端,还是做服务端,都在面向接口编程。而且IDL语法规则很简单,和C的结构体语法类似。
2.自动生成序列化及反序列化代码,让开发人员脱离了协议的细节,把更多的精力放到业务逻辑的编写上
听起来不错,事实上也真不错,不过还有几个问题需要考虑:
1.你的客户端能带上libprotobuf这个包吗?
2.你的客户端如果只支持C语言,怎么办?比如手机客户端
3.要读懂libprotobuf的编码原理,除了要有一定水平,还需要时间,其它项目成员能够接受吗?
我身边有朋友就碰到一种情况:一个手游项目,客户端是C的,服务端c++的,而且客户端和服务端是异地开发,如果从头做起,需要先协商好每个消息的结构,开发时这里不可避免的要涉及消息的序列化及反序列化,如果挨个消息手动解析,工作量会很大,而且调试困难,容易出错,可见一个像libprotobuf这样提供IDL的工具是很有必要的。
下面说一下,目前我的思路:
1.libprotobuf是肯定不能用了
2.为了便于双方理解,要更多的采用常规协议设计方法
3.一个小巧的IDL还是需要的,只需要自动完成序列化和反序列化即可
举个具体例子,假若有下面一个C的结构体:
struct SLogin
{
uint32 nId_;//用户ID
string sPwd_;//密码
uint8 nStatus_;//登陆状态
};
这里为了说明方便,使用了std::string,可以用char数组代替,或者用<ptr,len>形式代替。那么编码的时候,常规方式就是uint32占用4个字节,uint8占用1个字节,都容易理解,这里不要用libprotobuf的varint。
重点看一下string的编码方式,其中string可以是ASCII字符串,也可以是二进制的数据块。任何协议都遵循TLV结构,其中T为Type类型,L为Length长度,V为Value值,前面的uint32不用额外的TL是因为:(1)他是结构体的第一个成员,而第一个成员双方约定是整形,确定了T,(2)uint32本身说明了长度为4个字节。同样,由于双方约定第二个结构体成员是string类型,所以只需要确定长度即可。那么长度字段本身是固定长度,还是变长的呢?如果长度固定,最少需要2个字节,该情况下,对于很短的字符串也是2个字节的长度,有些浪费。所以最好采取变长的长度,varint,每个字节的低7位是有效承载,最高位表示后面是否还有高字节出现。
另外,针对上面这样简单结构体,最好用python脚本写个小代码生成工具,自动生成序列化及反序列化的代码,应该不难。
关于序列化,可以参考文本协议json做下对比,http://www.json.org/