网络服务器软件开发/中间件开发,关注ACE/ICE/boost

C++博客 首页 新随笔 联系 聚合 管理
  152 Posts :: 3 Stories :: 172 Comments :: 0 Trackbacks

    一.问题的提出

    这里的编码,具体是指如何高效地将基本类型序列化,以便在网络上传输,不是指将整个数据包用utf8或base64等编码,更不是应用层和业务相关的编码。

    假若有定义如下:

              #pragma pack(1)

              typedef struct tagS_HDR

              {

                     uint8       nProtocol;//协议类型

                     uint16     nLength;//消息长度,不包括本消息头

                     uint16     nType;//消息类型        

                     uint8       nExt;//协议扩展标记,比如消息子类型

              }S_HDR;

              #pragma pack()

              typedef struct tagS_FileTxHDR_Req

              {

                     S_HDR   hdr_;//消息头

                     uint32            nUID_;//接收方用户唯一ID

                     string             sFileName_;//要传送的文件名

                     uint32            nFileSize_;//文件大小

                     uint8              nFileID_;//临时分配的文件ID,最多支持同时传256个文件

              }S_FileTxHDR_Req;

    如果你是做服务器开发,对类似这样的结构体应该非常熟悉,为了简化问题,在继续讨论之前,我们做几个假设:

        (1)消息头的长度是固定的,且设计足够高效,不在编码优化之列。

        (2)整形里面只关注uint8,uint16.uint32.

        (3)字符串关注两类:以\0结尾的string和二进制流的bytes。

        (4)其它类型暂不关注,不影响问题的讨论,后续会慢慢完善。

       (5)libprotobuf已经很强大,但也有限制,比如必须链接一个库,如果是手机客户端和服务器通讯,显然是不能用的。

    场景如下:A(ID为123)要给B(ID为456)发送file.txt文件,文件内容为”Hello World!”,传统的序列化方式为nUID占用4个字节,nFileSize也占用4个字节,而事实上456用2个字节表示足以,” Hello World!”的长度用一个字节表示足以。明显是浪费嘛!有没有什么办法解决这个问题呢?当然有,答案是:站在巨人的肩上。

      

    二。Base 128 Varints

    Google Protobuf里面提出了“Base 128 Varints”编码,这是一种变字节长度的编码,官方描述为:varints是用一个或多个字节序列化整形的一种方法。我理解要点有三个(1)操作是序列化(2)操作对象是整形(3)变长编码。重点是最后一点,他是如何编码的呢?

       (1)除了最后一个字节,varint中的每个字节的最高位设为1,表示后面还有字节出现

       (2)每个字节的低7位看成是一个组(group),这个组和他相邻的下一个7位组共同存储某个整形的“组合表示”,最低有效组在前面。

    上面的定义可能比较生硬,我没看具体例子之前,也没搞明白,我们来看http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/encoding.html#varints中的例子:

        (1)一个字节。下面只有一个字节,所以最高位是0,表示十进制1

            0000 0001

        (2)两个字节。

            1010 1100 0000 0010

        由于第一个字节后面还有一个字节,所以第一个字节的最高位设置为1,表示后面还有后继字节,第二个字节的最高位为0。去掉每个字节的最高位,我们对两个字节进行分组。第一个7位组:0101100,第二个7位组:0000010,组合起来就是:0101100 0000010,由于最低有效组在前面,所以两个7位组进行调换,结果为:0000010  0101100,中间的空格是为了大家区分,去掉前面的0,也就是:100101100,十进制为1*2^8 + 1*2^5 + 1*2^3 + 1*2^2 = 256 + 32 + 8 + 4 = 300。

       (3)三个字节。我们换种方式,给定某个整形,要求写出他的varint表示,这里当然是落在需要3个字节表示的整形。16380可以吗?不可以!因为16380 < 2^14,所以2个字节足以,我们就以27491为例,27491的二进制表示为:

                     0110 1011 0110 0011

        注意这里是高字节之前,低字节在后,和varint的编码规则相反,首先按照7位一组划分为:0000001 1010110  1100011然后反转为:1100011 1010110  0000001,最后将末尾的7位组前面补0组成一个字节,两个7位组都补1分别组成一个字节:

                     11100011 11010110  00000001

       (4)更多字节。聪明的你可能发现,varint编码中4个字节最大表示的数为2^28,非常正确,同时说明了天下没有免费的午餐,有得有失。Protobuf引入了fixed32解决这个问题的,如果某个字段经常大于2^28,应当优选fixed32,他是固定长度为32位的。

     三.String和bytes

     string和bytes的表示形式一致,都是“长度+值”,其中长度采用varint编码,值为字符序列或者二进制码流

      

     如有错误,请指正,下次会写到结构体类型的编码,对应于libprotobuf的Message概念。

 

   


posted on 2009-09-11 04:12 true 阅读(2724) 评论(5)  编辑 收藏 引用 所属分类: 编码知识通信技术

Feedback

# re: 协议设计之一 基本类型的编码 2009-09-11 04:17 true
一篇文章竟然写了一晚,该休息会了:)  回复  更多评论
  

# re: 协议设计之一 基本类型的编码 2009-09-11 17:48 sd
省了微不足道的空间
却在序列化上耗费时间
得不偿失  回复  更多评论
  

# re: 协议设计之一 基本类型的编码 2009-09-11 17:58 true
@sd
我也知道xmpp已经得到了广泛的使用,这里主要是想,做一个自动描述协议,自动序列化的工具,既然要做到这样,我选择了基于libprotobuf的方案,第一次看到它的编码时,着实让人开阔思路:)  回复  更多评论
  

# re: 协议设计之一 基本类型的编码 2009-09-12 10:28 guest
mfc系列化中的CString的长度字段采用了类似的变长方案:
len < 255, 1Byte(byte)
255<= len <65535, 3Byte( 0xff, ushort)
len >= 65535, 7byte(0xff, 0xff, 0xff, uint)
因为大部分的字符串长度小于255(1个字节就够了), 使用3个字节都时候都很少了,需要使用7个字节的时候微乎其微。所以总体来说节省了空间,而且没有复杂的解码过程  回复  更多评论
  

# re: 协议设计之一 基本类型的编码 2009-09-25 15:55 那谁
@sd
赞同你的观点.
  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理