zoukankan      html  css  js  c++  java
  • google protobuf 数据类型_理解Protobuf数据格式解析

    什么是protobuf?

    Protobuf是Google开源的一款类似于json,XML数据交换格式,其内部数据是纯二进制格式,不依赖于语言和平台,具有简单,数据量小,快速等优点。目前用于序列化与反序列化官方支持的语言有C++,C#, GO, JAVA, PYTHON。适用于大小在1M以内的数据,因为像在移动设备平台,内存是很珍贵。
     

    protobuf格式的特点

    1. 效率高/性能好

    对比XML这种文件传输格式,解析时间大大低于XML解析时间,同时空间上的开销也减少了很多。

    2. 可以很方便的生成文件

    例如我们在进行文件传输时并不需要自己写很多东西,仅仅定义一个proto文件即可生成所需的代码。

    3. 支持“向后兼容”和“向前兼容”

    “向后兼容”(backward compatible),就是说,当模块B升级了后,它能够正确识别模块A发出的老版本的协议。由于老版本没有“状态”这个属性,在扩充协议时,可以考虑把“状态”属性设置成非必填 的,或者给“状态”属性设置一个缺省值即可。

    “向前兼容”(forward compatible),就是说,当模块A升级了之后, 模块B能够正常识别模块A发出的新版本的协议。这时候,新增加的“状态”属性会被忽略。因此其可扩展性也很强。

    4. 支持多种语言

    Google官方发布的源代码中包含了C++、Java、Python三种语言,这种天生就支持三种语言的格式简直少见,下面小编会使用python来为大家进行简单的讲解。

    资源网站大全 https://55wd.com 我的007办公资源网站 https://www.wode007.com

    Protobuf的使用

    使用方法也比较简单:

    • 定义用于消息文件.proto
    • 使用protobuf的编译器编译消息文件
    • 使用编译好对应语言的类文件进行消息的序列化与反序列化

    先来定义一个简单的消息:

    message Person {
       int32 id = 1;//24
       string name = 2;//wujingchao
       string email = 3;//wujingchao92@gmail.com
    }
    

    实际的二进制消息为:

    08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16 77 75 6a 69 6e 67 63 68 61 6f 39 32 40 67 6d 61 69 6c 2e 63 6f 6d

    下面就讲解这段二进制流数据是怎么组成的:

    varints

    一般情况下int类型都是固定4个字节,protobuf定义了一种变长的int,每个字节最高位表示后面还有没有字节,低7位就为实际的值,并且使用小端的表示方法。例如1,varint的表示方法就为:

    0000 0001
    

    是不是这样就省了三个字节。

    再例如300,4字节表示为:10 0101100,varint表示为:

    10101100 00000010
    

    所以前面消息为Person的id的值为00011000,即0x18。

    负数的最高位为1,如果负数也使用这种方式表示就会出现一个问题,int32总是需要5个字节,int64总是需要10个字节。

    所以定义了另外一种类型:sint32,sint64。采用ZigZag编码,所有的负数都使用正数表示,计算方式:

    sint32
    (n << 1) ^ (n >> 31)
    sint64
    (n << 1) ^ (n >> 63)
    Signed OriginalEncoded As
    0 0
    -1 1
    1 2
    -2 3
    2147483647 4294967294
    -2147483648 4294967295

    使用varint编码的类型有int32, int64, uint32, uint64, sint32, sint64, bool, enum。Java里面没有对应的无符号类型,int32与uint32一样。

    Wire Type

    每个消息项前面都会有对应的tag,才能解析对应的数据类型,表示tag的数据类型也是varint。

    tag的计算方式: (field_number << 3) | wire_type

    每种数据类型都有对应的wire_type:

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

    所以wire_type最多只能支持8种,目前有6种。

    所以前面Person的id,field_number为1,wire_type为0,所以对应的tag为

    1 <<< 3 | 0  = 0x08
    

    Person的name,field_number为2,wire_type为2,所以对应的tag为

    2 <<< 3 | 2 = 0x12
    

    对应Length-delimited的wire type,后面紧跟着的varint类型表示数据的字节数。

    所以name的tag后面紧跟的0x0a表示后面的数据长度为10个字节,即"wujingchao"的UTF-8 编码或者ASCII值:

    08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16

    嵌套的消息类型embedded messages与packed repeated fields也是使用这种方式表示,对应默认值的数据,是不会写进protobuf消息里面的。

    packed repeated与repeated的区别在于编码方式不一样,repeated将多个属性类型与值分开存储。而packed repeated采用Length-delimited方式。下面这个是官方文档的例子:

    message Test4 {
        repeated int32 d = 4 [packed=true];
    }
    
    22        // tag (field number 4, wire type 2)
    06        // payload size (6 bytes)
    03        // first element (varint 3)
    8E 02     // second element (varint 270)
    9E A7 05  // third element (varint 86942)
    

      

    如果没有packed的属性是这样存储的:

    20 //tag(field number 4,wire type 0)
    03 //first element (varint 3)
    20 //tag(field number 4,wire type 0)
    8E 02//second element (varint 270)
    20 //tag(field number 4,wire type 0)
    9E A7 05  // third element (varint 86942)
    

      

    是不是这种方式比较节省内存,所以proto3的repeated默认就是使用packed这种方式来存储。(proto2与proto3区别在于.proto的语法)。

  • 相关阅读:
    基于网络监听方式的电子邮件实现基础
    在一个存储过程里面执行另一个存储过程的应用
    Vim复制单个字符
    Linux下fsck修复文件系统
    (转载)2011年金山软件C++开发工程师笔试题 关于逆序输出
    (转载)C++ string详解
    (转载)Linux下网络API
    (转载)软中断和硬中断
    (转载)找工作经历总结百度offer
    (转载)Vim入门图解说明
  • 原文地址:https://www.cnblogs.com/ypppt/p/13356785.html
Copyright © 2011-2022 走看看