proto3 Message字段介绍|青训营笔记

223 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第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;
}

我们可以简单理解为定义了名为HelloRequestmessage类型,
其中包含一个名为name字段,该字段类型string

不难发现 我们在声明name字段的同时为该字段赋值为1
实际上,该值将作为这个字段在protobuf中的编号,我们称这个编号为tag

字段

字段类型

你可以在这个链接查看proto3中支持的所有字段类型与其对应生成的各语言字段类型。

在此我们以Go语言为例:

proto3Go特殊说明
doublefloat64
floatfloat32
int32int32可变长编码,对负数编码效率不佳,如果字段大多情况为负值,应考虑使用sint32
int64int64可变长编码,对负数编码效率不佳,如果字段大多情况为负值,应考虑使用sint64
uint32uint32可变长编码
uint64uint64可变长编码
sint32int32可变长编码,相较int32优化了对负数的编码效率
sint64int64可变长编码,相较int64优化了对负数的编码效率
fixed32uint32固定4byte编码,对于大数(2^28)比uint32效率更高
fixed64uint64固定8byte编码,对于大数(2^56)比uint32效率更高
sfixed32int32固定4byte编码
sfixed64int64固定8byte编码
boolbool
stringstring最长不超过2^32,必须使用UTF-87-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"`
}

注意到此时Messagestring类型。
接下来我们修改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类型。