Proto3语法入门
定义消息类型
首先让看一个非常简单的例子。假设要定义搜索请求消息格式,其中每个搜索请求都有一个查询字符串、感兴趣的特定结果页面以及每页的多个结果。这是.proto
用来定义消息类型的文件。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 该文件的第一行指定正在使用
proto3
语法:如果不这样做,协议缓冲区编译器将假定使用的是proto2。这必须是文件的第一个非空、非注释行。 - 所述
SearchRequest
消息定义指定了三个字段(名称/值对),一个用于每条数据要在此类型的消息包括。每个字段都有一个名称和一个类型。
指定字段规则
消息字段可以是以下之一:
- 单数:格式正确的消息可以有0个或一个该字段(但不超过一个)。这是 proto3 语法的默认字段规则。
repeated
:该字段可以在格式良好的消息中重复任意次数(包括0)。重复值的顺序将被保留。
在proto3中,标量数值类型的重复字段默认使用打包编码。
message 的定义语法:
<comment>
message <message_name> {
<filed_rule> <filed_type> <filed_name> = <field_number>
规则 类型 名称 编号
}
- comment: 注射 /* */或者 //
- message_name: 同一个pkg内,必须唯一
- filed_rule: 可以没有, 常用的有repeated, oneof
- filed_type: 数据类型, protobuf定义的数据类型, 生产代码的会映射成对应语言的数据类型
- filed_name: 字段名称, 同一个message 内必须唯一
- 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>
}
- enum_name: 枚举名称
- element_name: pkg内全局唯一, 很重要
- element_number: 必须从0开始, 0表示类型的默认值, 32-bit integer
别名
如果的确有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
如果想声明一个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;
Oneof
很像范型 比如 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
当无法明确定义数据类型的时候, 可以使用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
更新规范
- Don't change the field numbers for any existing fields.
更多请参考 Updating A Message Type