首先让看一个非常简单的例子。假设要定义搜索请求消息格式,其中每个搜索请求都有一个查询字符串、感兴趣的特定结果页面以及每页的多个结果。这是.proto
用来定义消息类型的文件。
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
proto3
语法:如果不这样做,协议缓冲区编译器将假定使用的是proto2。这必须是文件的第一个非空、非注释行。SearchRequest
消息定义指定了三个字段(名称/值对),一个用于每条数据要在此类型的消息包括。每个字段都有一个名称和一个类型。消息字段可以是以下之一:
repeated
:该字段可以在格式良好的消息中重复任意次数(包括0)。重复值的顺序将被保留。在proto3中,标量数值类型的重复字段默认使用打包编码。
message 的定义语法:
<comment> message <message_name> { <filed_rule> <filed_type> <filed_name> = <field_number> 类型 名称 编号 }
分配字段编号
消息定义中的每个字段都有一个唯一的编号。这些字段编号用于在消息二进制格式中标识字段,一旦消息类型被使用,就不应更改。
注意:1 到 15 范围内的字段编号占用一个字节进行编码,包括字段编号和字段类型。16 到 2047 范围内的字段编号占用两个字节。
指定的最小字段编号为 1,最大字段编号为 229 - 1 或 536,870,911。不能使用数字 19000 到 19999,因为它们是为 Protocol Buffers 实现保留的
如果你想保留一个编号,以备后来使用可以使用 reserved 关键字声明
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
消息字段可以具有以下类型之一:
上面就是所有的protobuf基础类型, 光有这些基础类型是不够的, 下面是protobuf提供的一些复合类型
使用enum来声明枚举类型:
enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; }
枚举声明语法:
enum <enum_name> { <element_name> = <element_number> }
如果的确有2个同名的枚举需求: 比如 TaskStatus 和 PipelineStatus 都需要Running,就可以添加一个: option allow_alias = true;
message MyMessage1 { enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; } } message MyMessage2 { enum EnumNotAllowingAlias { UNKNOWN = 0; STARTED = 1; // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside. } }
可以使用max
关键字指定保留的数值范围达到最大可能值。
enum Foo { UNIVERSAL = 0; WEB = 1; // IMAGES = 2; //Enum value 'IMAGES' uses reserved number 2 YOUTUBE = 3; reserved 2, 15, 9 to 11, 40 to max; reserved "FOO", "BAR"; }
同理枚举也支持预留值
message Result { int32 age = 1; string name = 2; } message SearchResponse { repeated Result result = 1; } // protoc -I=./ --go_out=./pbrpc/service --go_opt=module="github.com/ProtoDemo/pbrpc/service" pbrpc/service/test.proto // 会编译为: //type Result struct { // ... ... // Age int32 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` // Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` //} //type SearchResponse struct { // ... ... // Result []*Result `protobuf:"bytes,1,rep,name=result,proto3" json:"result,omitempty"` //}
如果想声明一个map, 可以如下进行:
message Project { int32 age = 1; string name = 2; } message MapData { map<string, Project> projects = 1; } // protoc -I=./ --go_out=./pbrpc/service --go_opt=module="github.com/ProtoDemo/pbrpc/service" pbrpc/service/test.proto // projects map[string, Project] // 会编译为: //type Project struct { // ... ... // Age int32 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` // Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` //} //type MapData struct { // ... ... // Projects map[string]*Project `protobuf:"bytes,1,rep,name=projects,proto3" json:"projects,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` //}
protobuf 声明map的语法:
map<key_type, value_type> map_field = N;
很像范型 比如 test_oneof 字段的类型 必须是 string name 和 SubMessage sub_message 其中之一:
message Sub1 { string name = 1; } message Sub2 { string name = 1; } message SampleMessage { oneof test_oneof { Sub1 sub1 = 1; Sub2 sub2 = 2; } } // protoc -I=./ --go_out=./pbrpc/service --go_opt=module="github.com/ProtoDemo/pbrpc/service" pbrpc/service/test.proto // 会编译为: //type SampleMessage struct { // state protoimpl.MessageState // sizeCache protoimpl.SizeCache // unknownFields protoimpl.UnknownFields // // // Types that are assignable to TestOneof: // // *SampleMessage_Sub1 // // *SampleMessage_Sub2 // TestOneof isSampleMessage_TestOneof `protobuf_oneof:"test_oneof"` //} // 操作使用 // of := &pb.SampleMessage{} // of.GetSub1() // of.GetSub2()
当无法明确定义数据类型的时候, 可以使用Any表示:
import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; } // protoc -I=./ -I=/usr/local/include --go_out=./pbrpc/service --go_opt=module="github.com/ProtoDemo/pbrpc/service" pbrpc/service/test.proto // 会编译为: // any本质上就是一个bytes数据结构 //type ErrorStatus struct { // state protoimpl.MessageState // sizeCache protoimpl.SizeCache // unknownFields protoimpl.UnknownFields // // Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` // Details []*anypb.Any `protobuf:"bytes,2,rep,name=details,proto3" json:"details,omitempty"` //}
any的定义
// `Any` contains an arbitrary serialized protocol buffer message along with a // URL that describes the type of the serialized message. // // Protobuf library provides support to pack/unpack Any values in the form // of utility functions or additional generated methods of the Any type. // Example 4: Pack and unpack a message in Go // // foo := &pb.Foo{...} // any, err := anypb.New(foo) // if err != nil { // ... // } // ... // foo := &pb.Foo{} // if err := any.UnmarshalTo(foo); err != nil { // ... // } // // The pack methods provided by protobuf library will by default use // 'type.googleapis.com/full.type.name' as the type URL and the unpack // methods only use the fully qualified type name after the last '/' // in the type URL, for example "foo.bar.com/x/y.z" will yield type // name "y.z". // // // JSON // ==== // The JSON representation of an `Any` value uses the regular // representation of the deserialized, embedded message, with an // additional field `@type` which contains the type URL. Example: // // package google.profile; // message Person { // string first_name = 1; // string last_name = 2; // } // // { // "@type": "type.googleapis.com/google.profile.Person", // "firstName": <string>, // "lastName": <string> // } // // If the embedded message type is well-known and has a custom JSON // representation, that representation will be embedded adding a field // `value` which holds the custom JSON in addition to the `@type` // field. Example (for message [google.protobuf.Duration][]): // // { // "@type": "type.googleapis.com/google.protobuf.Duration", // "value": "1.212s" // } // type Any struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ... // Note: this functionality is not currently available in the official // protobuf release, and it is not used for type URLs beginning with // type.googleapis.com. // // Schemes other than `http`, `https` (or the empty scheme) might be // used with implementation specific semantics. // TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"` // Must be a valid serialized protocol buffer of the above specified type. Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` }
可以再message里面嵌套message
message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 int64 ival = 1; bool booly = 2; } } message MiddleBB { // Level 1 message Inner { // Level 2 int32 ival = 1; bool booly = 2; } } }
与Go结构体嵌套一样, 但是不允许 匿名嵌套, 必须指定字段名称
import "google/protobuf/any.proto";
上面这情况就是读取的标准库, 在安装protoc的时候, 已经把改lib 挪到usr/local/include下面了,所以可以找到
如果proto文件并没有在/usr/local/include目录下, 如何导入,比如:
import "myproject/other_protos.proto";
通过-I 可以添加搜索的路径, 这样就编译器就可以找到引入的包了
引入后通过包的名称.变量的方式使用
比如要应用该结构中的ErrorStatus
syntax = "proto3"; import "google/protobuf/any.proto"; package hello; option go_package = "github.com/ProtoDemo/pbrpc/service"; message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; }
引入ErrorStatus
syntax = "proto3"; // 由于这个文件的pkg 也叫hello, 因此可以不用添加 pkg前缀 // 如果不是同一个pkg 就需要添加 pkg名称前缀, 比如hello.ErrorStatus import "pbrpc/service/test.proto"; package hello; option go_package = "github.com/ProtoDemo/pbrpc/service"; message ErrorStatusExt { ErrorStatus error_status = 1; } // protoc -I=./ -I=/usr/local/include --go_out=./pbrpc/service --go_opt=module="github.com/ProtoDemo/pbrpc/service" pbrpc/service/hello.proto
由于使用的import是相对路径, 因此必须在day21下编译, 这样编译器根据相对位置 才能找到pb/any.proto这个引用
更多请参考 Updating A Message Type