这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记
系统环境:ARM+MacOS+Go1.18+proto3
本笔记将详细解析官方Hello World示例中的message结构定义,
并补充说明message结构定义的数据类型与特殊规则。
Message介绍
就像它的名字那样,message在protobuf中主要用于实际传递的消息,一般会分为 请求(req) 和 响应(res) 两类。
让我们先看看示例代码中对请求与响应的message定义:
// HelloRequest 请求信息包含用户的名字.
message HelloRequest {
string name = 1;
}
// HelloReply 响应信息包含回复的内容
message HelloReply {
string message = 1;
}
我们可以简单理解为定义了名为HelloRequest的message类型,
其中包含一个名为name的字段,该字段类型为string。
不难发现 我们在声明name字段的同时为该字段赋值为1。
实际上,该值将作为这个字段在protobuf中的编号,我们称这个编号为tag。
字段
字段类型
你可以在这个链接查看proto3中支持的所有字段类型与其对应生成的各语言字段类型。
在此我们以Go语言为例:
| proto3 | Go | 特殊说明 |
|---|---|---|
| double | float64 | |
| float | float32 | |
| int32 | int32 | 可变长编码,对负数编码效率不佳,如果字段大多情况为负值,应考虑使用sint32 |
| int64 | int64 | 可变长编码,对负数编码效率不佳,如果字段大多情况为负值,应考虑使用sint64 |
| uint32 | uint32 | 可变长编码 |
| uint64 | uint64 | 可变长编码 |
| sint32 | int32 | 可变长编码,相较int32优化了对负数的编码效率 |
| sint64 | int64 | 可变长编码,相较int64优化了对负数的编码效率 |
| fixed32 | uint32 | 固定4byte编码,对于大数(2^28)比uint32效率更高 |
| fixed64 | uint64 | 固定8byte编码,对于大数(2^56)比uint32效率更高 |
| sfixed32 | int32 | 固定4byte编码 |
| sfixed64 | int64 | 固定8byte编码 |
| bool | bool | |
| string | string | 最长不超过2^32,必须使用UTF-8或7-bit ASCII字符 |
| bytes | []byte | 支持最长不超过2^32的任意bytes序列 |
字段规则
singular (单一字段)
singular是默认的字段规则,即不指定字段规则的字段将默认为singular。
在序列化singular字段时,若字段值为空,该字段不会占用空间。
// HelloRequest 请求信息包含用户的名字.
message HelloRequest {
string name = 1; // 该字段没有指定字段规则,因此默认为singular字段
}
注意: 与repeated不同,你不能显式的指明singular字段,
下面给出的代码会导致编译错误Missing field number。
// HelloRequest 请求信息包含用户的名字.
message HelloRequest {
singular string name = 1; // singular不能被显式的指定!
}
repeated (重复字段)
为字段指名repeated字段规则,将允许该字段在序列化信息中重复出现任意次数(包括0次)。
你可以简单的将repeated字段看作是Go语言中的slice,或C++中的vector。
下面我们将修改HelloReply信息,
为其中的message字段增加repeated字段规则。
在此之前,我们先看看没有指定repeated字段规则时生成的HelloReply结构体。
helloworld.pb.go L91
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
注意到此时Message为string类型。
接下来我们修改helloworld.proto文件,为HelloReply中的message添加repeated字段规则。
// HelloReply 响应信息包含回复的内容
message HelloReply {
repeated string message = 1; //只要在字段类型前添加repeated关键词就可以了
}
重新生成helloworld.pb.go文件。
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message []string `protobuf:"bytes,1,rep,name=message,proto3" json:"message,omitempty"`
}
注意到Message类型从string变为了[]string。
由于HelloReply中的Message类型已经变为了[]string,
我们需要重新构造类型正确的返回信息。
在本例中,我们将原信息按空格拆分为一个string类型的slice,代码如下:
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: strings.Split("Hello "+in.GetName(), " ")}, nil
}
DEMO验证
在完成前述修改后,我们运行服务端与客户端程序,看看是否能够正确的返回修改后的信息。
服务端:
➜ 1.hello_world git:(master) ✗ go run greeter_server/main.go --port 8888
2022/06/01 19:24:02 server listening at [::]:8888
2022/06/01 19:24:14 Received: user111
客户端:
➜ 1.hello_world git:(master) ✗ go run greeter_client/main.go --addr=localhost:8888 --name=user111
2022/06/01 19:24:14 Greeting: [Hello user111]
注意到客户端获得的Greeting信息已经变为了[]string类型。