zoukankan      html  css  js  c++  java
  • Protobuf3 编解码

    我们已经基本能够使用Protocol Buffers生成代码,编码,解析,输出及读入序列化数据。该篇主要讲述PB message的底层二进制格式。不了解该部分内容,并不影响我们在项目中使用Protocol Buffers,但是了解一下PB格式是如何做到smaller这一层,确实是很有必要的。Protobuf 序列化后所生成的二进制消息非常紧凑,这得益于 Protobuf 采用的非常巧妙的 Encoding 方法。

    1.什么是 Varint
    (1).Varint 是一种紧凑的表示数字的方法,它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。
    比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。
    (2).Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。
    因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010


    下图演示了 Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。
    Varint 编码

    图 6. Varint 编码

    消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对。如下图所示:
    Message Buffer

    图 7. Message Buffer

    采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。对于可选的 Field,如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field,这些特性都有助于节约消息本身的大小。
    二进制格式的message使用数字标签作为key,Key 用来标识具体的 field,在解包的时候,Protocol Buffer 根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 field。
    将 message编码后,key-values被编码成字节流存储。在message解码时,PB 解析器会跳过(忽略)不能够识别的字段,所以,message即使增加新的字段,也不会影响老程序代码,因为老程序代码根本就不能识别这些新添加的字段。
    上边我们说,“二进制格式的message使用数字标签作为key”,此处的数字标签,并非单纯的数字标签,而是数字标签与传输类型的组合,根据传输类型能够确定出值的长度。

    key的定义:

    (field_number << 3) | wire_type

    可以看到 Key 由两部分组成,第一部分是 field_number,第二部分为 wire_type。表示 Value 的传输类型,也就是说:key中的后三位,是值的wire_type类型。
    Wire Type 类型如下表所示:

    TypeMeaningUsed For
    0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
    1 64-bit fixed64, sfixed64, double
    2 Length-delimi string, bytes, embedded messages, packed repeated fields
    3 Start group Groups (deprecated)
    4 End group Groups (deprecated)
    5 32-bit fixed32, sfixed32, float

    举个例子来分析protobuf数据编码和解码,如下所示:

    message Test1 {
        required int32 a = 1;    
    }
    //.......protobuf读写操作..........
    Test1 test;
    test.set_a(150);
    //.....将数据序列化到文件.....

    写入message后,用UltraEdit打开,二进制格式查看,我们看到最终输出文件中包含三个数字:08 96 01(十六进制),这是如何得来的呢?
    1.首先来解析tag

    2.至此我们知道数字的field_number=1,值类型为varint。根据上面讲解来解码96 01,即为150:

    96 01 = 1001 0110  0000 0001
           → 000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)
           → 10010110
           → 2 + 4 + 16 + 128 = 150

    注意:数值部分,低位在前,高位在后。

    2.protobuf负数表示方式
    在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用 Varint 表示一个负数,那么一定需要 10 个 byte长度。为此 Google Protocol Buffer 定义了 sint32 这种类型,采用 zigzag 编码。将所有整数映射成无符号整数,然后再采用varint编码方式编码,这样绝对值小的整数,编码后也会有一个较小的varint编码值。
    Zigzag 编码用无符号数来表示有符号数字,正数和负数交错,这就是 zigzag 这个词的含义了。

    图 8. ZigZag 编码

    使用 zigzag 编码,绝对值小的数字,无论正负都可以采用较少的 byte 来表示,充分利用了 Varint 这种技术。
    其他的数据类型,比如字符串等则采用类似数据库中的 varchar 的表示方法,即用一个 varint 表示长度,然后将其余部分紧跟在这个长度部分之后即可。

    Zigzag映射函数为:

    Zigzag(n) = (n << 1) ^ (n >> 31);    //n为sint32时
    Zigzag(n) = (n << 1) ^ (n >> 63);    //n为sint64时

    按照这种方法,-1将会被编码成1,1将会被编码成2,-2会被编码成3,如下表所示:

    Signed OriginalEncoded As
    0 0
    -1 1
    1 2
    -2 3
    2 4
    -3 5
    2147483647 4294967294
    -2147483648 4294967295

    3.Non-varint 数字
    Non-varint数字比较简单,double 、fixed64 的Wire Type:1,在解析式告诉解析器,该类型的数据需要一个64位大小的数据块即可。同理,float和fixed32的Wire Type:5,给其32位数据块即可。两种情况下,都是高位在后,低位在前。

    4.String类型
    Wire Type:2的数据,是一种指定长度的编码方式:key+length+content,key的编码方式是统一的,length采用varints编码方式,content就是由length指定长度的Bytes。定义如下的message格式:

    message Test2 {
        required string b = 2;
    }

    设置该值为"testing",二进制格式查看:12 07 74 65 73 74 69 6e 67
    红色字节为“testing”的UTF8代码,此处,key是16进制表示的,所以展开是:12 -> 0001 0010,后三位010为wire type = 2,0001 0010右移三位为0000 0010,即tag=2。
    length此处为7,后边跟着7个bytes,即我们的字符创"testing"。

    字段顺序
    简单来说只有两点:
        编码/解码与字段顺序无关,这一点由key-value机制就能保证
        对于未知的字段,编码的时候会把它写在序列化完的已知字段后面

  • 相关阅读:
    Mac OS X上安装 Ruby运行环境
    MAC 命令行工具(Command Line Tools)安装
    如何快速正确的安装 Ruby, Rails 运行环境
    安裝 Rails 開發環境
    用模块化编程
    阅读技术书籍
    NHibernate构建一个ASP.NET MVC应用程序
    SQL注入
    Redis
    Code digest
  • 原文地址:https://www.cnblogs.com/DswCnblog/p/6703664.html
Copyright © 2011-2022 走看看