Protocol Buffer是Google提供的一种数据序列化协议,是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
protocol 语言文件后面名为.proto。
文件第一行指定版本。必须在文件首行指定,之前不能有任何空行和注释。可以不指定,默认为proto2。
syntax = "proto3";以message关键字开头,然后指定名称。消息体中时字段的定义,分别指定类型、名称和字段编号。
message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3;}示例中只展示了基础类型字段定义,同样可以指定其他枚举类型或者定义好的Message类型。
在二进制格式中,字段编号与类型标识符结合使用。 1到15范围内的字段编号需要一个字节来编码。 从 16 到 2,047 的数字需要 2 个字节。 所以字段编号 1 到 15 的单字节标识符提供更好的性能,所以应将其用于最基本、最常用的字段。
protocol 基础类型与其他语言类型对应关系 Scalar Value Types
关于字段编号是1到2的29次方减一之间的数。不能使用19000到19999之间的数 (FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber))。
message SearchResponse { repeated Result results = 1;}message Result { string url = 1; string title = 2; repeated string snippets = 3;}repeated关键字表面该字段是一个重复的值,生成对应语言代码时,会是一个集合字段或属性。
如果更新服务的消息删除某些字段,应保证不应重复使用该字段编号。如从现有的message中删除字段应该保留其编号。
message Foo { reserved 2, 15, 9 to 11; //to表示一个连续的范围值 reserved "foo", "bar"; int32 id = 1; string name = 3;}也可以将reserved 关键字用作未来可能添加字段的占位符。
可以通过编号和名称的方式保留字段,但是不能混合使用
protocol 和很多主流语言注释方式相同,使用 // 和 /* ... */的注释方式。
对于声明的message编码过后,其中定义的字段,会有一个默认的零值。如:
通过enum关键字定义枚举,并什么可以有哪些值。
enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; }可以看到枚举内的第一个常量定义为0,这是必须的,proto3中所有的字段都是必须的(proto3移除了proto2中required和optional的声明),需要定义一个0的常量作为默认值。
如果枚举不需要共用,可以直接在message内声明并定义,如:
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 选项。
enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; }enum Foo { reserved 2, 15, 9 to 11, 40 to max; reserved "FOO", "BAR";}和message定义保留字段一样,不支持序号和名称混合使用。
通过package关键字指定包名,方便工程化管理,避免命名冲突。对应Go中包名,C#的命名空间。
package foo.bar;import引入其他proto文件,对应Go的import,C#的using。
import "google/api/annotations.proto";proto3 和 proto2 不同版本间定义的message类型可以相互引用,但是proto2 定义的枚举不能被proto3 引入使用。
默认情况下,您只能使用直接导入的 .proto文件定义。然而,有时需要移动 .proto文件到一个新的位置,但不想为此更新所有引用它的.proto文件,此时可以在文件原始位置放置一个仿造的 .proto文件,使用import public将所有导入转发到新位置。任何包含import public语句的proto文件都可以临时依赖import public依赖。例如:
// new.proto// All definitions are moved here// old.proto// This is the proto that all clients are importing.import public "new.proto";import "other.proto";// client.protoimport "old.proto";// You use definitions from old.proto and new.proto, but not other.proto编译器通过
-I/--proto_path参数指定搜索导入的文件的目录。如果没有指定,默认会在调用编译器的目录中查找。通常,您应该将--proto_path标志设置为项目的根目录,并对所有导入使用完全限定的名称。
可以在一个message内部定义一个message,类似C#、java中的内部类
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1;}通过_Parent_._Type_的形式,在外部重复使用定义的嵌套类型
message SomeOtherMessage { SearchResponse.Result result = 1;}如果有对现有message跟新的需求,例如在不破坏现有代码的前提下新增字段。应遵循如下规则:
OBSOLETE_前缀进行标识,也可以通过预留字段保留字段编号,确保不会重复使用该编号。未知字段是格式良好的协议缓冲区序列化数据,用于表示解析器无法识别的字段。proto3早期版本中会丢弃未知字段。3.5之后的版本会在解析期间保留未知字段,并包含在序列化输出中以兼容proto2。
Any类型允许将消息作为嵌入类型使用不需要在proto内定义。 类型 Any 可以表示任何已知的 Protobuf 消息类型。使用Any类型需要引入google/protobuf/any.proto包。
import "google/protobuf/any.proto";message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2;}如果消息中包含多个字段,但是最多同时只会设置一个值,可以借助Oneof强制约束并节省内存(oneof集中所有的字段共享内存)。
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; }}设置oneof字段将自动清除oneof字段的其他所有成员。如果设置了几个oneof字段,只有最后一个字段仍然有值。
SampleMessage message;message.set_name("name");CHECK(message.has_name());message.mutable_sub_message(); // Will clear name field.CHECK(!message.has_name());
oneof集内的字段必须具有唯一的字段编号。oneof中可以添加任何类型的字段但是不能使用repeated字段。但是可以在repeated字段类型内使用oneof关键字。
map关键字可以很方便的声明一个map类型字段:
//map<key_type, value_type> map_field = N;map<string, Project> projects = 3;key_type可以是任何整数或字符串类型(除float和bytes外的任何标量类型)。
声明map时不能和repeated一起使用,可以通过如下方式变相定义一个重复的map
message Order { message Attributes { map<string, string> values = 1; } repeated Attributes attributes = 1;}或者
message MapFieldEntry { key_type key = 1; value_type value = 2;}repeated MapFieldEntry map_field = N;定义Rpc约束,即声明传入和返回值,可以理解为其他语言中接口(抽象)的定义。
service BlogService { rpc CreateArticle (CreateArticleRequest) returns (CreateArticleReply) { option (google.api.http) = { post: "/v1/article/" body: "*" }; }}选项(Options)不会影响整体声明,改变的时编译时的处理方式,在google/protobuf/descriptor.proto查看完整支持的options。
Options分为文件级(只能声明在文件最顶级)、消息级(什么在message内)、字段级(声明field)。
选项也可以声明在enum 、service等类型上。官网文档原话Options can also be written on enum types, enum values, oneof fields, service types, and service methods; however, no useful options currently exist for any of these.刚接触还不是很理解后面这段话的含义。
如果有需要自定义选项,参考文档 。