一、简介


Protocol Buffers是谷歌定义的一种跨语言、跨平台、可扩展的数据传输及存储的协议,因为将字段协议分别放在传输两端,传输数据中只包含数据本身,不需要包含字段说明,所以传输数据量小,解析效率高。一条消息用protobuf序列化后的大小是json的10分之一。类似的序列化框架还有Thrift、avro。thrift和avro都提供rpc服务和序列化,而protocol buffer只是提供序列化功能。

二、安装


安装Google的protoc编译器,这个工具可以把proto文件中定义的Message转换为各种编程语言中的类。下载release版本直接编译安装。

https://github.com/google/protobuf/

3.1.0及以下版本,不支持PHP,需要安装插件

https://github.com/bramp/protoc-gen-php、https://github.com/chobie/protoc-gen-php、https://github.com/drslump/Protobuf-PHP

/usr/local/protobuf/bin/protoc --help 查看有没有--php_out选项

三、应用


1、限定修饰符

Required

​ 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

Optional(singular)

​ 表示是一个可选字段,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。

​ 因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。

 

 

Repeated

​ 表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。

如果没有给optional和repeated字段赋值,那么字段是不会出现在序列化后的数据中的。

2、数据类型

3、PHP示例应用

1)、编写proto文件,结构化数据被称为 Message

vim user.proto

syntax = "proto3";

message userInfo{

​ int32 id = 1;

​ string name = 2;

}

2)、编译成目标语言类文件(PHP)


/usr/local/protobuf/bin/protoc user.proto --php_out=/pb/php/

3)、PHP调用

require(…);

$pbUserInfo = new userInfo();

$pbUserInfo->setId(1);

$pbUserInfo->setName("echo");

$pbRs = $pbUserInfo->encode();

4、序列化解析

Protobuf消息由字段(field)构成,每个字段有其规则(rule)、数据类型(type)、字段名(name)、tag,以及选项(option)。序列化时,消息字段会按照tag顺序,以key+val的格式,编码成二进制数据。

即一个消息就是多个字段的序列拼接成的一个二进制字节流,这种方式就像Key-Value的方式。但这种方式组织的数据并不需要额外的分隔符来划分数据,所以其可以减低序列化结果的大小。

Protobuf消息序列化之后,会产生二进制数据。这些数据(精确到bit)按照含义不同,可以划分为6个部分:MSB flag、tag、编码后数据类型(wire type)、长度(length)、字段值(value)、以及填充(padding)

1)、key-value

value

value根据不同的类型采用的编码方式也不同,如果是整型,采用二进制表示;如果是字符,会直接原样写入文件或者字符串(即不编码)。

key是以Varint编码存储

一个message的key由两部分组成,一部分是在定义消息时对字段的编号(field_num),另一部分是字段类型(wire_type)。

key = tag << 3 | wire_type。也就是说,key的第一个字节后3个位是wire type,剩下的位是tag值。

所以,第一个字节还剩下4个二进制位(8-1-3)用于表示tag的值,如果tag值大于15则需增加字节来表示。

因为只用3个二进制位表示wire type,所以最多只能支持8种,目前有6种。Protobuf支持丰富的数据类型,但是编码之后,只剩下Varint(0)、64-bit(1)、Length-delimited(2)、satrt group(3)、end group(4)和32-bit(5)类型。

2)、wire Type


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

Type Meaning Used 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


3)、Varint


是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。

Varint中的每个 字节 的最高位 有特殊的含义,如果该位为 1,表示后续的字节也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 位都是用来表示数字。因此小于等于 127 的数字都可以用一个 byte 表示。大于等于 127 的数字,

比如 300,会用两个字节来表示:

1010 1100 0000 0010

去掉两个最高位MSB flag之后为:

010 1100 **000 0010**

protobuf字节序是小端字节序,所以这个数字实际是

000 0010 010 1100(100101100 == 300)

所以用varint存储一个int32的小数值,最多是可以节约3个字节。为了用尽可能节约字节编码消息,Protobuf在多处都使用了Varint这种格式。比如数据类型里的int32、int64,以及tag值和后面将要解释的length值,都使用Varint类型存储。

Variant编码也有两个不好的地方:

4)、固定长度编码(32-bit、64-bit)

第一,不利于表示大数。对于比较小的数来说,以0到127为例,用Varint很划算。以浪费1bit和少量额外的计算为代价,只要1个字节就可以表示。但是对于比较大的数,就不划算了。以int32为例,大于2^(4*7) - 1的数(每个字节只有7个位用于存储),需要用5个字节来表示。比如268435456 (2^28)

$pbUserInfo->setId(268435456);

08 80 80 80 80 01

也就是说,如果某个消息的某个int字段大部分时候都会取比较大的数,那么这个字段使用Varint这种变长类型来编码就没什么好处。对于这种情况,Protobuf定义了64-bit和32-bit两种定长编码类型。使用64-bit编码的数据类型包括fixed64、sfixed64和double;使用32-bit编码的数据类型包括fixed32、sfixed32和float。以userInfo消息id字段(float)为例:

syntax = "proto3";

message userInfo{

​ float id = 1;

​ string name = 2;

}

$pbUserInfo->setId(268435456);

0d 00 00 80 4d

5)、ZigZag


第二个缺点是不适合表示负数,

如果负数也使用这种方式表示就会出现一个问题,

int32总是需要5(+1,key占1个)个字节,int64总是需要10个字节(加上KEY,1个字节)。

syntax = "proto3";

message userInfo{

​ int64 id = 1;

​ string name = 2;

}

$pbUserInfo->setId(-1);

如下图所示(int64):

为了克服这个缺陷,Protobuf提供了sint32和sint64两种数据类型。如果某个消息的某个字段出现负数值的可能性比较大,那么应该使用sint32或sint64。这两种数据类型在编码时,会先使用ZigZag编码将负数映射成正数,然后再使用Varint编码。

ZigZag编码计算公式为:

sint32

(n << 1) ^ (n >> 31)

sint64

(n << 1) ^ (n >> 63)

ZigZag编码规则如下图所示:

图1

图2

6)、Length-delimited


如前所述,64-bit和32-bit是定长编码格式,长度固定。Varint是变长编码格式,长度由字节的MSB(最高位)决定。Length-delimited编码格式则会将数据的length也编码进最终数据,使用Length-delimited编码格式的数据类型包括string、bytes和自定义消息。

syntax = "proto3";

message userInfo{

​ int64 id = 1;

​ string name = 2;

}

$pbUserInfo->setName(“hello”);

12 05 68 65 6c 6c 6f

7)、repeated

前面讨论的字段都是optional类型,最多只有一个val,但是repeated修饰符,可以有多个val。

message userInfo{

​ int64 id = 1;

​ string name = 2;

​ repeated int32 prop = 3;

}

$pbUserInfo->getProp(

)[] = 1;

$pbUserInfo->getProp()[] = 2;

$pbUserInfo->getProp()[] = 3;

序列化之后的数据如下图所示:

18 01 18 02 18 03

repeated字段就是简单的把每个字段值依次序列化而已。

8)、packed

如果repeated字段包含的val比较多,那么每个val都带上key是比较浪费的

message userInfo{

​ int64 id = 1;

​ string name = 2;

​ repeated int32 prop = 3 [packed=true];

}

序列化之后的数据如下图所示:

1a 03 01 02 03

如果repeated字段设置了packed选项,则会使用Length-delimited格式来编码字段值。

5、proto3和proto2区别

1)、proto文件中的第一行非空白非注释行syntax = “proto3"表示使用proto3的语法,否则默认使用proto2的语法

2)、字段移除required,将optional改名为singular。如果不加repeated,默认就是singular的。

3)、语言增加了Go,Ruby,JavaNano等的支持未来还计划支持PHP等

4)、移除了default选项在proto2中,可使用default为field指定默认值。在proto3中,field的默认值只依赖于field的类型,不再能够被指定。当field的value为默认值时,该field不会被序列化,可节省空间。不要依赖于字段的默认值的行为,因为无法区分是指定为默认值,还是未定义值。

5)、枚举类型的第一个枚举值必须是0,proto3中必须提供一个枚举值为0作为枚举的默认值。为了和proto2兼容(proto2使用第一个枚举值作为默认值),因此规定一个枚举值为0。

6)、不再支持group,proto2中已经不推荐使用group。proto3中不再支持group。group可以用embedded message来实现。

7)、不再支持Extension,新增Any关键字proto3中不再支持Extension, 除了用在custom option。

6、其他

Any、oneOf、Maps、Packages、Json Mapping

END

google protobuf序列化原理解析 (PHP示例)的更多相关文章

  1. 常见的序列化框架及Protobuf序列化原理

    原文链接:https://www.jianshu.com/p/657fbf347934 https://www.cnblogs.com/javazhiyin/p/11375553.html https ...

  2. google protobuf的原理和思路提炼

    之前其实已经用了5篇文章完整地分析了protobuf的原理.回过头去看,感觉一方面篇幅过大,另一方面过于追求细节和源码,对protobuf的初学者并不十分友好,因此这篇文章将会站在"了解.使 ...

  3. Google protobuf序列化以及反序列化

    序列化的目的是将对象持久化到硬盘或者用于网络传输.java也提供了序列化技术,非常简单,只要实现Serializable接口即可.如下: public class commonService impl ...

  4. Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?

    前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存 ...

  5. 《精通并发与Netty》学习笔记(05 - Google Protobuf与Netty的结合)

    protobuf是由Google开发的一套对数据结构进行序列化的方法,可用做通信协议,数据存储格式,等等.其特点是不限语言.不限平台.扩展性强 Netty也提供了对Protobuf的天然支持,我们今天 ...

  6. python protobuf序列化repeated运用

    下面是proto描述文件的定义 message Person { required string name = 1; required int32 id = 2; optional string em ...

  7. Google protobuf解析消息逻辑的版本问题

    在分析caffe2源码的过程中,由于caffe2使用protobuf作为网络结构和网络参数序列化和反序列化的机制,想在反序列化之前进行加解密处理,这是反向protouf其实有两个版本的实现来进行消息的 ...

  8. Skinned Mesh原理解析和一个最简单的实现示例

    Skinned Mesh 原理解析和一个最简单的实现示例   作者:n5 Email: happyfirecn##yahoo.com.cn Blog: http://blog.csdn.net/n5 ...

  9. google protobuf 使用示例

    定义.proto接口文件 package tutorial; message Person { required ; required int32 id = ; //unique ID number ...

随机推荐

  1. Response.End方法

    文章:在try...catch语句中执行Response.End()后如何停止执行catch语句中的内容 调用Response.End()方法能保证,只输出End方法之前的内容. 调用Context. ...

  2. 用URL传参带特殊字符,特殊字符丢失

    文章:URL中编码URL特殊字符 文章:用URL传参带特殊字符,特殊字符丢失(encode) 如果url中有特殊字符,需要对url进行编码,否则特殊字符丢失,导致最终接收到的值不对.

  3. python学习笔记03:python的核心数据类型

    从根本上讲,Python是一种面向对象的语言.它的类模块支持多态,操作符重载和多重继承等高级概念,并且以Python特有的简洁的语法和类型,OOP十分易于使用.Python的语法简单,容易上手. Py ...

  4. iOS- Apple零配置网络协议Bonjour的使用?

    1.前言 这段时间为了解决公司App的网络离线需求,做了个Apple推出的零配置网络协议Bonjour的Test,主要是为了解决iOS设备的IP获取,之前是可以使用socket的广播来实现,但是使用A ...

  5. Hive整体优化策略

    一 整体架构优化 现在hive的整体框架如下,计算引擎不仅仅支持Map/Reduce,并且还支持Tez.Spark等.根据不同的计算引擎又可以使用不同的资源调度和存储系统. 整体架构优化点: 1 根据 ...

  6. 个人作业4 alpha阶段 个人总结

    一.个人总结 二.回答问题 三.再提问题 Q1:关于第三章过早优化 过早优化:既然软件是"软"的,那么它就有很大的可塑性,可以不断改进.放眼望去,一个复杂的软件似乎很多的模块都可以 ...

  7. SpringData——HelloWorld

    1.背景 最开始了解SpringData的时候,以为他不就是ORM的一种实现方式嘛,还能有什么新的东西.从hibernate到ibatis.mybatis,也许他只不过是spring想整合一个更方便的 ...

  8. Redis的概述和简单使用(转载)

    文章来源:http://jingyan.baidu.com/article/db55b60996d0124ba30a2f92.html Redis是一个基于key-value的高速缓存系统,类似于me ...

  9. 用c++读取文件夹中的所有文件名

    //头文件,注意要加stdafx.h和io.h等 #include "stdafx.h" #include <io.h> #include <vector> ...

  10. BZOJ 1597 土地购买(斜率优化DP)

    如果有一块土地的长和宽都小于另一块土地的长和宽,显然这块土地属于“赠送土地”. 我们可以排序一下将这些赠送土地全部忽略掉,一定不会影响到答案. 那么剩下的土地就是长递减,宽递增的.令dp[i]表示购买 ...