简介
protocol buffer是google的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法,例如XML,不过它比xml更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构
定义一个消息类型
一个简单的.proto文件
在.proto文件定义消息,message是.proto文件最小的逻辑单元,由一系列name-value键值对构成。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
// This is an annotation
-
syntax: 注明使用的语法,常用的又proto2和proto3两种
-
在一个message中,定义的每个字段由字段限制,字段类型,字段名和编号四部分组成
- 字段限制
required: 必须赋值的字段(proto3没有了) optional: 可有可无的字段 repeated: 可重复的字段
注意:使用required字段一定要小心,因为该字段是永久性的。如果以后因为某种原因,想不用该字段,或者要将该字段改成optional或者repeated,那么使用旧接口读取新的协议时,如果发现没有该字段,他们会认为该字段是不完整的,会拒接接收该消息,或者直接丢弃。
2. 字段类型.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type Dart Type double double double float float64 Float double float double float float float float float32 Float float float double int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int int int32 Fixnum or Bignum (as required) int integer int int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long int/long[3] int64 Bignum long integer/string[5] Int64 uint32 Uses variable-length encoding. uint32 int[1] int/long[3] uint32 Fixnum or Bignum (as required) uint integer int uint64 Uses variable-length encoding. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5] Int64 sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int int int32 Fixnum or Bignum (as required) int integer int sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long int/long[3] int64 Bignum long integer/string[5] Int64 fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int[1] int/long[3] uint32 Fixnum or Bignum (as required) uint integer int fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5] Int64 sfixed32 Always four bytes. int32 int int int32 Fixnum or Bignum (as required) int integer int sfixed64 Always eight bytes. int64 long int/long[3] int64 Bignum long integer/string[5] Int64 bool bool boolean bool bool TrueClass/FalseClass bool boolean bool string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String str/unicode[4] string String (UTF-8) string string String bytes May contain any arbitrary sequence of bytes. string ByteString str []byte String (ASCII-8BIT) ByteString string 除了这些基本类型,也可以是自定义的message类型 - 字段编号
消息中的每一个字段都有一个独一无二的数值类型的编号。1到15使用一个字节编码,16到2047使用2个字节编码,所以应该将编号1到15留给频繁使用的字段。
可以指定的最小的编号为1,最大为2^{29}-1或536,870,911。但是不能使用19000到19999之间的值,这些值是预留给protocol buffer的。 -
//用于注释
预留字段
如果消息的字段被移除或注释掉,但是使用者可能重复使用字段编码,就有可能导致例如数据损坏、隐私漏洞等问题。一种避免此类问题的方法就是指明这些删除的字段是保留的。如果有用户使用这些字段的编号,protocol buffer编译器会发出告警。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
默认值(proto3默认值不能指定了)
如果没有指定默认值,则会使用系统默认值,对于string默认值为空字符串,对于bool默认值为false,对于数值类型默认值为0,对于enum默认值为定义中的第一个元素,对于repeated默认值为空。
message PhoneNumber {
required string number=1;
optional PhoneType type=2 [default=HOME];
}
枚举类型
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
通过设置可选参数allow_alias为true,就可以在枚举结构中使用别名(两个值元素值相同)
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
由于枚举值采用varint编码,所以为了提高效率,不建议枚举值取负数。这些枚举值可以在其他消息定义中重复使用。
使用其他消息类型
可以使用一个消息的定义作为另一个消息的字段类型
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
你还可以导入其他的.proto文件
import "myproject/other_protos.proto";
嵌套类型
在protocol中可以定义如下的嵌套类型
message TsirResponse{
message Man{
int sex = 1;
float weight = 2;
}
Man base = 1;
}
如果在另外一个消息中需要使用Man定义,则可以通过Parent.Type来使用
message OtherMessage {
TsirResponse.Man other = 1;
}
更新一个数据类型
在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:
- 不要修改已经存在字段的标签号。
- 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
- 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。 - optional和repeated限定符也是相互兼容的。
任意消息类型Any
Any类型是一种不需要在.proto文件中定义就可以直接使用的消息类型,使用前import google/protobuf/any.proto文件即可。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
Oneof
消息中的多个字段类型在同一时刻只有一个字段会被使用,使用case()或WhichOneof()方法来检测哪个字段被使用了。
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
Maps表映射
protocol buffers提供了简介的语法来实现map类型:
map<key_type, value_type> map_field = N;
key_type可以是除浮点指针或bytes外的其他基本类型,value_type可以是任意类型
map<string, Project> projects = 3;
- Map的字段不可以是重复的(repeated)
- 线性顺序和map值的的迭代顺序是未定义的,所以不能期待map的元素是有序的
- maps可以通过key来排序,数值类型的key通过比较数值进行排序
- 线性解析或者合并的时候,如果出现重复的key值,最后一个key将被使用。从文本格式来解析map,如果出现重复key值则解析失败。
包
命名空间,用来防止名称冲突
package foo.bar;
定义服务
如果想在RPC系统中使用消息类型,就需要在.proto文件中定义RPC服务接口,然后使用编译器生成对应语言的存根。
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
JSON映射
Proto3支持JSON格式的编码。编码后的JSON数据的如果没有值或值为空,解析时protocol buffer将会使用默认值,在对JSON编码时可以节省空间。
proto3 | JSON | JSON example | Notes |
---|---|---|---|
message | object | {“fBar”: v, “g”: null, …} | Generates JSON objects. Message field names are mapped to lowerCamelCase and become JSON object keys. null is accepted and treated as the default value of the corresponding field type. |
enum | string | “FOO_BAR” | The name of the enum value as specified in proto is used. |
map< K,V> | object | {“k”: v, …} | All keys are converted to strings. |
repeated V | array | [v, …] | null is accepted as the empty list []. |
bool | true, false | true, false | |
string | string | “Hello World!” | |
bytes | base64 string | “YWJjMTIzIT8kKiYoKSctPUB+” | |
int32, fixed32, uint32 | number | 1, -10, 0 | JSON value will be a decimal number. Either numbers or strings are accepted. |
int64, fixed64, uint64 | string | “1”, “-10” | JSON value will be a decimal string. Either numbers or strings are accepted. |
float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON value will be a number or one of the special string values “NaN”, “Infinity”, and “-Infinity”. Either numbers or strings are accepted. Exponent notation is also accepted. |
Any | object | {“@type”: “url”, “f”: v, … } | If the Any contains a value that has a special JSON mapping, it will be converted as follows: {“@type”: xxx, |
Timestamp | string | “1972-01-01T10:00:20.021Z” | Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits. |
Duration | string | “1.000340012s”, “1s” | Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision. Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision. |
Struct | object | { … } | Any JSON object. See struct.proto. |
Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer. |
FieldMask | string | “f.fooBar,h” | See fieldmask.proto. |
ListValue | array | [foo, bar, …] | |
Value | value | Any JSON value | |
NullValue | null | JSON null |