Proto 文件语法
hanpy

学习一下 proto 文件的语法和使用规则

一、定义语法

1.0、 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 指定proto语法版本
syntax = "proto3";

// 生成的go文件放在那个目录下面以及包名是什么
// . 代表当前目录, service代表生成的go文件的包名是service
option go_package = ".;service";

// 定义服务,名字叫 HelloGrpc
// 服务中有一个rpc方法,名为SayHe11o
// 这个方法会发送一个HelloRequest,然后返回一个HelloResponse。
service HelloGrpc {
rpc SayHello(HelloRequest) returns (HelloResponse);
}

// message 有点类似结构体
// "=" 并不是赋值的意义, 是定义这个变量在 message 中的位置
message HelloRequest {
string requestName = 1;
}

message HelloResponse {
string respName = 1;
}

1.1、syntax

用来标记当前使用 proto 的哪个版本,如果不设置默认会使用 proto2

1.2、option

选项信息,生成的 Go 文件放在那个目录下面以及包名是什么

1.3、message

声明消息的关键字,类似 Go 语言中的 struct。消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型个 proto 文件中可以定义多个消息类型。

字段规则

  1. required:消息体中必填字段,如果不设置就导致编码异常(protobuf2 中使用,在 protobuf3 中已经去除)
  2. optional:消息体中可选字段,protobuf3 中默认
  3. repeate:消息体中可重复字段,重复的值的顺序会被保留在 Go 中重复的会被定义为切片
  4. reserved:标记的标识号、字段名,都不能在当前消息中使用
  5. enum:定义枚举类型
  6. map:定义map类型

消息号

每个字段都有唯一的一个数字标识符,一旦开始使用就不能够再改变,范围是 [1,2^29-1]内的整数。

1.3、service

使用关键字 service 定义一个 RPC 服务接口,使用 rpc 定义具体方法,而消息类型则充当方法的参数和返回值。

1
2
3
service HelloGrpc {
rpc SayHello(HelloRequest) returns (HelloResponse);
}

二、Proto 类型与 Go 类型

.proto Go Notes
double float64
float float32
int32 int32 对于负值的效率很低,如果有负值,使用sint64
uint32 uint32 使用变长编码
uint64 uint64 使用变长编码
sint32 int32 负值时比int32高效的多
sint64 int64 使用变长编码,有符号的整型值。编码时比通常的int64高效。
fixed32 uint32 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。
fixed64 uint64 是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。

三、简单使用

3.1、嵌套消息

proto 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 指定proto语法版本
syntax = "proto3";

// 生成的go文件放在那个目录下面以及包名是什么
// . 代表当前目录, service代表生成的go文件的包名是service
option go_package = ".;service";

message Addr {
string home = 1;
}

message Class {
string className = 1;
}

message User {
Addr addr = 1;
Class class = 2;
string name = 3;
}

Go 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Addr *Addr `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
Class *Class `protobuf:"bytes,2,opt,name=class,proto3" json:"class,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
}

type Addr struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Home string `protobuf:"bytes,1,opt,name=home,proto3" json:"home,omitempty"`
}

type Class struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

ClassName string `protobuf:"bytes,1,opt,name=className,proto3" json:"className,omitempty"`
}

3.2、切片

proto 文件

1
2
3
4
5
6
7
8
9
10
11
12
// 指定proto语法版本
syntax = "proto3";

// 生成的go文件放在那个目录下面以及包名是什么
// . 代表当前目录, service代表生成的go文件的包名是service
option go_package = ".;service";

message list {
repeated int32 listInt = 1;
repeated string listString = 2;
repeated float listFloat = 3;
}

生成的 Go 文件

1
2
3
4
5
6
7
8
9
type List struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

ListInt []int32 `protobuf:"varint,1,rep,packed,name=listInt,proto3" json:"listInt,omitempty"`
ListString []string `protobuf:"bytes,2,rep,name=listString,proto3" json:"listString,omitempty"`
ListFloat []float32 `protobuf:"fixed32,3,rep,packed,name=listFloat,proto3" json:"listFloat,omitempty"`
}

3.3、Map

proto 文件

1
2
3
4
5
6
7
8
9
10
11
// 指定proto语法版本
syntax = "proto3";

// 生成的go文件放在那个目录下面以及包名是什么
// . 代表当前目录, service代表生成的go文件的包名是service
option go_package = ".;service";

message mapList {
int32 userId = 1;
map<string,string> like =2;
}

Go 文件

1
2
3
4
5
6
7
8
type MapList struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"`
Like map[string]string `protobuf:"bytes,2,rep,name=like,proto3" json:"like,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}

3.4、枚举

proto 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 指定proto语法版本
syntax = "proto3";

// 生成的go文件放在那个目录下面以及包名是什么
// . 代表当前目录, service代表生成的go文件的包名是service
option go_package = ".;service";

// 枚举消息
message EnumMsg {
enum Gender{
// 枚举字段标识符,必须从0开始
UnKnown = 0;
Body = 1;
Girl = 2;
}
// 使用自定义的枚举类型
Gender sex = 2;
}

Go 文件

1
2
3
4
5
6
7
8
type EnumMsg_Gender int32

const (
// 枚举字段标识符,必须从0开始
EnumMsg_UnKnown EnumMsg_Gender = 0
EnumMsg_Body EnumMsg_Gender = 1
EnumMsg_Girl EnumMsg_Gender = 2
)

3.5、保留标识符

proto 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 指定proto语法版本
syntax = "proto3";

// 生成的go文件放在那个目录下面以及包名是什么
// . 代表当前目录, service代表生成的go文件的包名是service
option go_package = ".;service";

// 保留
message demo {
// 标记 1 和 3 在当前消息不可以用
reserved 1,3;
string name = 1;
}

message demo1 {
// 字段 name 和 age 不能使用
reserved "name","age";
string name = 1;
}

生成 Go 文件

1
2
3
4
5
# 会提示错误
$ protoc --go-grpc_out=. --go_out=. demo.proto
demo.proto: Field "name" uses reserved number 1.
demo.proto:17:10: Field name "name" is reserved.
demo.proto: Suggested field numbers for demo: 2