前言
龙湘君在去年时候写过一篇《IT人的成长 二》,介绍了Protobuf的优缺点,今天作者接着这个话题来介绍Protobuf的数据结构和使用方法。
Protocol Buffers,简称Protobuf,是一种跨语言、多平台、具有非常良好拓展性的结构化数据协议。它由Google开发,并且经过在Google内部使用后,成为了开源项目。Profobuf和XML一样,都可以作为网络传输协议。但是熟悉XML的人都知道,XML在解析速度上慢的非常惊人。一个简单的Protobuf例子如下所示,将如下消息定义在person.proto文件中:
message PersonInfo
{
required int32 id = 1;
optional bytes address = 2;
repeated string telephone = 3;
}
这就定义好了消息PersonInfo,它有三种字段,分别为id,address,telephone。每个字段后面有一个唯一的标识号,它会决定序列化时每个字段在二进制文件中的位置。
required int32 id = 1
id字段是int32类型,而且是必须设置值的required字段,否则无法对消息进行解析。
optional bytes address = 2
address也是bytes类型,因为bytes类型可以处理中文等多字节的字符。它的限定修饰符是optional,说明它是一个可选字段。对于消息发送方而言,无论设置该字段与否,消息接收方都能够解析,并且对未设置的字段自动忽略。
repeated string telephone = 3
telephone是string类型,而且是repeated的。repeated的字段可以包含0~N个元素,可以视作一个容器类型。
以C++编程为例,通过在https://github.com/google/protobuf/releases上下载protoc-$VERSION-win32.zip后,解压得到Protocol Compiler编译器protoc.exe。编译的命令为
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/person.proto
$SRC_DIR和$DST_DIR分别代表了输入文件位置和输出文件位置。如果将protoc.exe和person.proto文件放在同一目录下,则在当前路径打开cmd,输入
Protoc –I=.\ --cpp_out=.\ person.proto
编译成功后,可以得到person.pb.h和person.pb.cc两个文件。将.h和.cc文件包含进C++文件中,就可以创建PersonInfo的类型了。下面介绍一下具体的一些使用方法:
消息发送方设置传输的Protobuf消息,序列化到string中
PersonInfo person_info;
person_info.set_id(1);
person_info.set_address("福华一路6号");
person_info.add_telephone("12345678");
std::string str;
person_info.SerializeToString(str);
消息接收方从string中解析出Protobuf消息
PersonInfo person_info;
std::string str;
person_info.ParseFromString(str);
int id = person_info.id();
std::string address = person_info.address();
std::string telephone = person_info.telephone(0);
1字段赋值
对于optional和required的字段通过set方法就可以设置字段值了,如
void set_id(__int32 value)
void set_address(const std::string value)
对于repeated的字段,通过add方法来增加字段值,或者通过set方法修改字段值。
void add_telephone(const std::string value)
void set_telephone(int index, const std::string value)
2字段读取
对于optional和required的字段,直接用字段名称作为函数名,就可以得到字段值了。
__int32id();
const std::stringaddress();
对于repeated的字段,使用下标作为参数即可得到对应的字段值。
const std::stringtelephone(int index);
3序列化
Protobuf中最常见的序列化是将消息实例序列化到array或者string中,分别通过如下两个函数进行
bool SerializeToArray(void* data, int size)
bool SerializeToString(string* output)
除此之外,Protobuf还提供了序列化到Ostream、CodedStream、FileDeor等对象的方法。
4反序列化
消息接收方将Protobuf消息从对象中反序列化出来,与序列化方法相对应,如从array或者string中反序列化的具体函数为
bool ParseFromArray(void* data, int size)
bool ParseFromString(string* output)
当然序列化和反序列化都是有可能失败的,必要时可以通过返回值来判断。
Protobuf文件的数据结构字段有required、optional、repeated三种,在上文中已经分别介绍过。消息发送方必须设置required字段的值,否则无法将其序列化和发送。optional字段可以不设置值,在实际应用中也是最常见的字段。对于repeated的字段,如果字段是其他message类型或者string类型,那么内部存储的是它的指针,除了add、set方法外还可以通过set_allocated的方式设置值。对于其它类型的字段,存储的是它的拷贝。
Protobuf文件的常用数据结构可以映射为C++、Java以及Python的各类数据结构,如下表所示,更完整的信息可以在https://developers.google.com/protocol-buffers/docs/proto中找到。
Proto
C++
Java
Python
double
double
double
Float
float
float
float
Float
int32
int32
int
int
int64
int64
long
int/long
uint32
uint32
int
int/long
uint64
uint64
long
int/long
bool
bool
boolean
bool
string
string
String
str/unicode
bytes
string
ByteString
str
ProtoBuf可以使用嵌套类,可以在一个消息中包含另一个消息。ProtoBuf也可以使用枚举变量,和C++一样使用关键词enum。另外,它也支持通过package来定义命名空间。
使用ProtoBuf作为网络传输协议,可以解决一系列由于平台差异、语言差异等引起的问题,而且有着非常快的解析速度和较小的空间占用。目前作者仍在不断使用ProtoBuf作为服务器和客户端之间数据传输的协议,并配合使用另一个开源工具ZeroMQ作为消息通讯的工具,有兴趣的朋友们可以了解一下。