Protobuf 是 Google 开发的语言中立、平台中立的结构化数据序列化和反序列化协议。用于应用程序间结构化数据的传输,相对于JSON、XML等基于文本的协议,它以二进制方式传输数据,效率更高。
有关 protobuf 详细介绍请参见官方文档 Protocol Buffers Documentation
在实际使用中发现官方文档对消息体中各种不同类型数据的构建和解析没有特别详细的说明,特别是复杂数据。本文分享在实际使用中的经验,抛砖引玉,希望对大家会有所帮助。
以下的示例均以Python语言为例来说明。
下面的示例基于官方文档中包含的 helloword例子修改。用户信息 UserInfo 包括用户名、用户ID和组ID列表,其中组ID列表就是基本数据类型 int32 的列表。
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message UserInfo {
string name = 1;
int32 uid = 2;
repeated int32 gid = 3;
}
message HelloRequest {
UserInfo user = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
构建方式如下所示:
from __future__ import print_function
import os
import logging
import getpass
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
print("Will try to greet world ...")
with grpc.insecure_channel("localhost:50051") as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
user = getpass.getuser()
uid = os.getuid()
gids = os.getgroups()
ui = helloworld_pb2.UserInfo()
ui.name = user
ui.uid = uid
for g in gids:
ui.gid.append(g)
response = stub.SayHello(helloworld_pb2.HelloRequest(user=ui))
print("Greeter client received: " + response.message)
if __name__ == "__main__":
logging.basicConfig()
run()
对于基本数据类型的列表,在创建对应消息的实例时已经创建了空的列表,因此通过Python操作列表的接口操作即可。例如示例中直接通过 append() 将用户的所有组ID加入到列表中。
解析时,通过常规列表API即可
gids = ','.join([str(x) for x in request.user.gid])
在消息体中 map 数据类型和 Python 中的字典类型,比如以下示例中的 course 表示每门课程的名称的得分。
...
message UserInfo {
string name = 1;
int32 uid = 2;
repeated int32 gid = 3;
map course = 4;
...
构建方式与 Python 中字典的构建方式相同。
...
ui = helloworld_pb2.UserInfo()
ui.name = user
...
ui.course['foo'] = 5
ui.course['bar'] = 10
...
解析方法按Python中字典的方式即可。
course = ' '.join([f"{k}:{v}" for k,v in request.user.course.items()])
即列表中的每一项为一个字典。
在 proto 定义中增加 Friend 并修改 UserInfo 如下所求,friend_list 即为列表,列表中的每个元素为字典 friend
message Friend {
map friend = 1;
}
message UserInfo {
string name = 1;
int32 uid = 2;
repeated int32 gid = 3;
map course = 4;
repeated Friend friend_list = 5;
}
构建方式如下:
...
ui = helloworld_pb2.UserInfo()
...
ui.friend_list.append(helloworld_pb2.Friend(friend={'Bob':12}))
ui.friend_list.append(helloworld_pb2.Friend(friend={'Peter':13}))
ui.friend_list.append(helloworld_pb2.Friend(friend={'Alice':11}))
...
friend_list 为列表,操作方式同上。但对于列表中的字典元素,需要单独创建对应的类实例。创建方法可参考对应消息类的构造函数,消息类的定义在 .pyi 文件中。
...
class Friend(_message.Message):
__slots__ = ("friend",)
class FriendEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: int
def __init__(self, key: _Optional[str] = ..., value: _Optional[int] = ...) -> None: ...
FRIEND_FIELD_NUMBER: _ClassVar[int]
friend: _containers.ScalarMap[str, int]
def __init__(self, friend: _Optional[_Mapping[str, int]] = ...) -> None: ...
解析方式如下所示:
friends = ' '.join([f"{name}:{age}" for x in request.user.friend_list for name, age in x.friend.items()])
其它复杂数据结构不外乎由基本数据类型、字典、列表组合而成。总之,对于各类包含复杂数据结构消息的构建和解析,都可以参考 .pyi 中消息类的定义,从而找到消息构建和解析方法。