Protobuf即Protocol Buffers,是Google公司开发的一种跨语言和平台的序列化数据结构的方式,是一个灵活的、高效的用于序列化数据的协议。
与XML和JSON格式相比,protobuf更小、更快、更便捷。protobuf是跨语言的,而且自带一个编译器(protoc),只须要用protoc进行编译,就能够编译成Java、Python、C++、C#、Go等多种语言代码,而后能够直接使用,不须要再写其它代码,自带有解析的代码。
只须要将要被序列化的结构化数据定义一次(在.proto文件定义),即可以使用特别生成的源代码(使用protobuf提供的生成工具)轻松的使用不一样的数据流完成对结构数据的读写操做。甚至能够更新.proto文件中对数据结构的定义而不会破坏依赖旧格式编译出来的程序。
GitHub地址:https://github.com/protocolbuffers/protobuf
不一样语言源码版本下载地址:
https://github.com/protocolbuffers/protobuf/releases/latestphp
Protobuf的优势以下:
A、性能号,效率高
序列化后字节占用空间比XML少3-10倍,序列化的时间效率比XML快20-100倍。
B、有代码生成机制
将对结构化数据的操做封装成一个类,便于使用。
C、支持向后和向前兼容
当客户端和服务器同时使用一块协议的时候, 当客户端在协议中增长一个字节,并不会影响客户端的使用
D、支持多种编程语言
Protobuf目前已经支持Java,C++,Python、Go、Ruby等多种语言。java
Protobuf的缺点以下:
A、二进制格式致使可读性差
B、缺少自描述python
下载C++版本的Protobuf源码protobuf-cpp-3.6.1.tar.gz
解压Protobuf源码:tar -zxvf protobuf-cpp-3.6.1.tar.gz
进入protobuf-3.6.1源码目录:cd protobuf-3.6.1
配置变量:./configure --prefix=/usr/local/protobuf
编译:make
检查、测试:make check
安装:sudo make install
设置环境变量:git
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib export PATH=$PATH:/usr/local/protobuf/bin
检查版本号:protoc --version
github
Protobuf提供了protoc编译器,用于经过定义好的.proto文件来生成Java,Python,C++,Ruby,Objective-C,C#,Go等语言代码。protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
(1)导入目录设置
IMPORT_PATH声明了一个.proto文件所在的解析import具体目录。若是忽略该值,则使用当前目录。若是有多个目录则能够屡次调用--proto_path,会顺序的被访问并执行导入。-I=IMPORT_PATH是--proto_path的简化形式。
(2)生成代码指定编程
--cpp_out :在目标目录DST_DIR中产生C++代码 --java_out :在目标目录DST_DIR中产生Java代码 --python_out :在目标目录 DST_DIR 中产生Python代码 --go_out :在目标目录 DST_DIR 中产生Go代码 --ruby_out:在目标目录 DST_DIR 中产生Ruby代码 --javanano_out:在目标目录DST_DIR中生成JavaNano --objc_out:在目标目录DST_DIR中产生Object代码 --csharp_out:在目标目录DST_DIR中产生Object代码 --php_out:在目标目录DST_DIR中产生Object代码
(3)导入proto消息文件指定
必须指定一个或多个.proto文件做为输入,多个.proto文件能够只指定一次。虽然文件路径是相对于当前目录的,每一个文件必须位于其IMPORT_PATH下,以便每一个文件能够肯定其规范的名称。
(4)生成编程语言相关代码
当用Protobuf编译器来运行.proto文件时,编译器将生成所选择语言的代码,相应语言的代码能够操做在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中以及从一个输入流中解析消息。
对C++语言,编译器会为每一个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每个消息有一个对应的类。
对Java语言,编译器为每个消息类型生成了一个.java文件以及一个特殊的Builder类(用来建立消息类接口的)。
对Go语言,编译器会为每一个消息类型生成了一个.pb.go文件。
对Ruby语言,编译器会为每一个消息类型生成了一个.rb文件。api
Protobuf中,消息即结构化数据。安全
message Person { string name = 1; int32 id = 2; string email = 3; }
Person消息格式有3个字段,在消息中承载的数据分别对应于每个字段,其中每一个字段都有一个名字和一种类型。
在一个消息文件.proto中能够定义多个消息类型,在定义多个相关的消息的时候较为有用。ruby
// [START declaration] syntax = "proto3"; package Company.Person; import "google/protobuf/timestamp.proto"; // [END declaration] // [START messages] message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5; } // Our address book file is just one of these. message AddressBook { repeated Person people = 1; } // [END messages]
.proto文件中非注释非空的第一行必须使用Proto版本声明,版本声明以下:
syntax = "proto3";
若是不使用proto3版本声明,Protobuf编译器默认使用proto2版本。
Proto消息文件的命名以下:
packageName.MessageName.proto
packageName为package声明的包名
MessageName为消息名称服务器
添加注释可使用C/C++/java风格的双斜杠(//)语法格式。
.proto文件中能够新增一个可选的package声明符,用来防止不一样的消息类型有命名冲突。包的声明符会根据使用语言的不一样影响生成的代码:
A、对于C++语言,产生的类会被包装在C++的命名空间中。
B、对于Java语言,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package。
C、对于Go语言,包能够被用作Go包名称,除非显式的提供一个option go_package在.proto文件中。
Protobuf语法中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每一个包会被看做是其父类包的内部类。固然对于Company.Person以“.”分隔的是从最外围开始的。
Protobuf编译器会解析.proto文件中定义的全部类型名。 对于不一样语言的代码生成器会知道如何来指向每一个具体的类型,即便它们使用了不一样的规则。
字段类型包括标量类型和合成类型。
标量类型包括:
合成类型包括枚举(enumerations)或其它消息类型。
在消息定义中,每一个字段都有惟一的一个数字标识符。标识符用来在消息的二进制格式中识别各个字段,一旦使用就不可以再改变。
最小的标识符能够从1开始,最大到2^29 - 1(536,870,911),不可使用其中[19000-19999]( Protobuf协议实现中进行了预留,从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber)的标识号。若是非要在.proto文件中使用预留标识符,编译时就会报警。
[1,15]内的标识号在编码的时候会占用一个字节。[16,2047]以内的标识号则占用2个字节。因此应该为频繁出现的消息元素保留[1,15]内的标识号。
消息的字段修饰符必须是以下之一:
A、singular:一个格式良好的message应该有0个或者1个该字段(但不能超过1个)。
B、repeated:在一个格式良好的消息中,该字段能够重复任意屡次(包括0次),重复值的顺序会被保留。
在proto3中,repeated的标量字段默认状况下使用packed。
若是经过删除或者注释全部字段,之后的用户在更新消息类型的时候可能重用标识符。若是使用旧版本代码加载相同的.proto文件会致使严重的问题,包括数据损坏、隐私错误等等。为了确保不会发生向前兼容能够为字段tag(reserved name可能会JSON序列化的问题)指定reserved标识符,Protobuf编译器会警告将来尝试使用相应字段标识符的用户。
不要在同一行reserved声明中同时声明字段名字和标识符。
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
当一个消息被解析的时候,若是编码消息里不包含一个特定的singular元素,被解析的对象所对应的字段被设置为一个默认值,不一样类型默认值以下:
对于string,默认是一个空string
对于bytes,默认是一个空的bytes
对于bool,默认是false
对于数值类型,默认是0
对于枚举,默认是第一个定义的枚举值,必须为0
对于消息类型(message),字段没有被设置,确切的消息是根据语言肯定的,一般状况下是对应语言中空列表。
对于标量消息字段,一旦消息被解析,就没法判断字段是被设置为默认值仍是根本没有被设置,应该在定义消息类型时注意。
当定义一个消息类型时,须要为消息中的某个字段指定预约义值序列中的一个值,此时可使用枚举定义预约以序列。如为Person消息添加一个PhoneType类型的字段,PhoneType类型的值多是MOBILE,HOME,WORK。
message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5; }
每一个枚举类型必须将其第一个类型映射为0。
能够经过allow_alias选项为true,将不一样的枚举常量指定为相同的值,不然编译器会在别名的地方产生一个错误信息。
enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; } enum EnumNotAllowingAlias { UNKNOWN = 0; //EnumNotAllowingAlias中没有设置allow_alias STARTED = 1; // RUNNING = 1;//error }
枚举常量必须在32位整型值的范围内。由于enum值是使用可变编码方式的,对负数不够高效,所以不推荐在enum中使用负数。
能够在一个消息定义的内部或外部定义枚举,枚举能够在.proto文件中的任何消息定义里重用。能够在一个消息中声明一个枚举类型,而在另外一个不一样的消息中使用枚举(采用MessageType.EnumType的语法格式)。
当对一个使用了枚举的.proto文件运行Protobuf编译器的时候,生成的代码中将有一个对应的enum(Java或C++),被用来在运行时生成的类中建立一系列的整型值符号常量(symbolic constants)。
在反序列化的过程当中,没法识别的枚举值会被保存在消息中。对支持开放枚举类型超出指定范围外的语言(例如C++和Go),未识别的值会被表示成所支持的整型;对封闭枚举类型的语言中(Java),使用枚举中的一个类型来表示未识别的值,而且可使用所支持整型来访问;在其它状况下,若是解析的消息被序列号,未识别的值将保持原样。
能够将其它消息类型用做字段类型。对于同一个消息文件内部定义的消息,能够在其它消息内部直接引用消息类型;对于在其它消息文件定义的消息类型,能够经过导入其余消息文件中的定义来使用相应的消息类型。如使用google.protobuf.Timestamp消息类型须要导入相应消息文件:import "google/protobuf/timestamp.proto";
若是要在父消息类型的外部重用消息类型,须要以Parent.Type的形式使用。
Any类型消息容许在没有指定.proto定义的状况下使用消息做为一个嵌套类型。一个Any类型包括一个能够被序列化bytes类型的任意消息以及一个URL做为一个全局标识符和解析消息类型。
为了使用Any类型,须要导入import google/protobuf/any.proto。
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; }
对于给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename
。
不一样语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值。例如在java中,Any类型会有特殊的pack()和unpack()访问器,在C++中会有PackFrom()和UnpackTo()方法。
Oneof定义用来表明在实现的时候,该组属性中有且只能有一个被定义,不能出现多个。
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }
上述定义中只能出现name或者sub_message的出现,不能同时出现,同时Oneof不能出现repeated域。重复传递值到Oneof多个域仅仅最后的会生效,其它的将被忽略掉。
若是要建立一个关联映射,Protobuf提供了一种快捷的语法:
map<key_type, value_type> map_field = N;
其中key_type能够是任意Integer或者string类型(除了floating和bytes的任意标量类型均可以),value_type能够是任意类型,但不能是map类型。
例如,建立一个Project的映射,每一个Projecct使用一个string做为key:
map<string, Project> projects = 3;
Map的字段能够是repeated。
序列化后的顺序和map迭代器的顺序是不肯定的,因此不要指望以固定顺序处理Map
当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序。
从序列化中解析或者融合时,若是有重复的key则后一个key不会被使用,当从文本格式中解析map时,若是存在重复的key。
向后兼容性问题
map语法序列化后等同于以下内容,所以即便是不支持map语法的Protobuf实现也能够处理数据:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
若是想要将消息类型用在RPC(远程方法调用)系统中,能够在.proto文件中定义一个RPC服务接口,Protobuf编译器将会根据所选择的不一样语言生成服务接口代码及stub。如要定义一个RPC服务并具备一个方法Search,Search方法可以接收SearchRequest并返回一个SearchResponse,能够在.proto文件中进行以下定义:
service SearchService { rpc Search (SearchRequest) returns (SearchResponse); }
最直观的使用Protobuf的RPC系统是gRPC,由谷歌开发的语言和平台中的开源的PRC系统,gRPC在使用Protobuf时很是有效,若是使用特殊的Protobuf插件能够直接从.proto文件中产生相关的RPC代码。
若是不想使用gRPC,可使用Protobuf用于本身的RPC实现。
Proto3支持JSON的编码规范,便于在不一样系统之间共享数据。
若是JSON编码的数据丢失或者其自己是null,数据会在解析成Protobuf的时候被表示成默认值。若是一个字段在Protobuf中表示为默认值,会在转化成JSON编码的时候忽略掉以节省空间。
若是一个已有的消息格式已没法知足新的需求,须要在要息中添加一个额外的字段,但同时旧版本写的代码仍然可用。可使用更新消息解决,更新消息而不破坏已有代码是很是简单的。更新消息时规则以下:
A、不要更改任何已有字段的标识符。
B、若是增长新的字段,使用旧格式的字段仍然能够被新产生的代码所解析。应该记住元素的默认值,新代码就能够以适当的方式和旧代码产生的数据交互。经过新代码产生的消息也能够被旧代码解析,但新增长的字段会被忽视掉。未被识别的字段会在反序列化的过程当中丢弃掉,若是消息再被传递给新的代码,新的字段依然是不可用的。
C、非required的字段能够移除。只要标识符在新的消息类型中再也不使用(推荐重命名字段,例如在字段前添加“OBSOLETE_”
前缀)。
D、int32, uint32, int64, uint64,和bool是所有兼容的,能够相互转换,而不会破坏向前、 向后的兼容性。
E、sint32和sint64是互相兼容的,但与其它整数类型不兼容。
F、string和bytes是兼容的——只要bytes是有效的UTF-8编码。
G、嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
H、fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
I、枚举类型与int32,uint32,int64和uint64相兼容(注意若是值不相兼容则会被截断),然而在客户端反序列化后可能会有不一样的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但表示方式会依照语言而定。int类型的字段总会保留他们的
J、能够添加新的optional或repeated的字段, 但必须使用新的标识符(消息中从未使用过的标识符,不能使用已经被删除过的标识符)。
在定义.proto文件时可以标注一系列的options。options并不改变整个文件声明的含义,但却可以影响特定环境下处理方式。完整的可用选项能够在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它能够做用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它能够用在消息定义的内部。固然有些选项能够做用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并无一种有效的选项能做用于全部的类型
optimize_for(文件选项): 能够被设置为LITE_RUNTIME,SPEED,CODE_SIZE。这些值将经过以下的方式影响C++及Java代码的生成:
SPEED (default): Protobuf编译器将经过在消息类型上执行序列化、语法分析及其它通用的操做,生成的代码最优。
CODE_SIZE:Protobuf编译器将会产生最少许的类,经过共享或基于反射的代码来实现序列化、语法分析及各类其它操做。采用CODE_SIZE方式产生的代码将比SPEED要少得多,但操做要相对慢些。CODE_SIZE方式生成代码中实现的类及其对外的API与SPEED模式都是同样的,经常使用在一些包含大量的.proto文件并且并不盲目追求速度的应用中。
LITE_RUNTIME:Protobuf编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite替代libprotobuf)。libprotobuf-lite核心类库因为忽略了一些描述符及反射,要比全类库小得多。这种模式常常在移动手机平台应用多一些。编译器采用LITE_RUNTIME模式产生的方法实现与SPEED模式不相上下,产生的类经过实现MessageLite接口,但仅仅是Messager接口的一个子集。option optimize_for = CODE_SIZE;
cc_enable_arenas(文件选项):对于C++产生的代码启用arena allocation。
objc_class_prefix(文件选项):设置Objective-C类的前缀,添加到全部Objective-C今后.proto文件产生的类和枚举类型。没有默认值,所使用的前缀应该是×××荐的3-5个大写字符,注意2个字节的前缀是苹果所保留的。
deprecated(字段选项):若是设置为true则表示该字段已经被废弃,而且不该该在新的代码中使用。在大多数语言中没有实际的意义。int32 old_field = 6 [deprecated=true];
java_package (file option):指定生成java类所在的包,若是在.proto文件中没有明确的声明java_package,会使用默认包名。不须要生成java代码时不起做用。
java_outer_classname (file option):指定生成Java类的名称,若是在.proto文件中没有明确声明java_outer_classname,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),不须要生成java代码时不起任何做用
objc_class_prefix (file option): 指定Objective-C类前缀,会前置在全部类和枚举类型名以前。没有默认值,应该使用3-5个大写字母。注意全部2个字母的前缀是Apple保留的。
Proto文件编码规范以下:A、描述文件以.proto作为文件后缀。B、除结构定义外的语句以分号结尾,结构定义包括:message、service、enum;rpc方法定义结尾的分号无关紧要。C、Message命名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式。D、Enums类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式。E、Service与rpc方法名统一采用驼峰式命名。