一.什么是protobuf
protobuf全称Google Protocol Buffers,是google开发的的一套用于数据存储,网络通信时用于协议编解码的工具库。它和XML或者JSON差不多,也就是把某种数据结构的信息,以某种格式(XML,JSON)保存起来,protobuf与XML和JSON不同在于,protobuf是基于二进制的。主要用于数据存储、传输协议格式等场合。那既然有了XML等工具,为什么还要开发protobuf呢?主要是因为性能,包括时间开销和空间开销:
1.时间开销:XML格式化(序列化)和XML解析(反序列化)的时间开销是很大的,在很多时间性能上要求很高的场合,你能做的就是看着XML干瞪眼了。
2.空间开销:熟悉XML语法的同学应该知道,XML格式为了有较好的可读性,引入了一些冗余的文本信息。所以空间开销也不是太好(应该说是很差,通常需要实际内容好几倍的空间)。
据实验(当然不是我实验),一条消息数据,用protobuf序列化后的大小是json格式的十分之一,xml格式的二十分之一。
这一篇主要讲protobuf用作数据存储方面,下一篇讲用作rpc通讯协议方面。
二.使用protobuf
注:我只会讲语言特性,至于环境配置之类的,请大家自行Google。
protobuf的使用很简单,开发人员按照一定的语法定义结构化的消息格式,然后用自带的变异工具,工具将自动生成相关的类,官方支持java、c++、python语言环境(当然可以在网上找到很多支持其他语言的封装,当然你也可以自己写一个,只要符合google定义的格式)。通过将这些类包含在项目中,可以很轻松的调用相关方法来完成业务消息的序列化与反序列化工作。
1.定义报文格式。protobuf文件的后缀是proto,是一种类似c++或者java的语法。使用protoc.exe(windows平台下,你可以下载源码编译,也可以网上直接下载exe,这个大家自行Google)把proto文件编译成c++,java或者Python就可以使用了。
package my; message Person { required string name = 1; optional int32 age = 2; };
在这里定义了一个Person,我们只是简单的定义了name和age。这里注意,在上例中,package 名字叫做 my,定义了一个消息 Person,该消息有2个成员,类型为 string 的 name,另一个为类型为 int32 的成员 age。optional 代表这是一个可选的成员,即消息中可以不包含该成员,required代表是必须的。
写好proto之后把proto放在protoc.exe相同目录,然后在此目录使用指令:protoc --cpp_out=d:\proto person.proto(我们使用的是c++)。当用protocolbuffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
就可以生成person.pb.h,person.pb.cc.在生成的头文件中,定义了一个
C++ 类 Person,继承自google::protobuf::Message,后面我们进行对Person数据的文件读写, 将使用这个类来对消息进行操作。诸如对消息的成员进行赋值,将消息序列化等等都有相应的方法。
首先,把数据写入disk:
#include "person.pb.h" … int main(void) { Person person; person.set_name("flamingo"); person.set_age(18); // Write fstream output("./log", ios::out | ios::trunc | ios::binary); if (!person.SerializeToOstream(&output)) { cerr << "Failed to write msg." << endl; return -1; } return 0; }
先定义一个Person,然后给Person赋值,其中set_name,set_age是根据proto自动生成的,我们使用SerializeToOstream
将对象序列化成二进制(导致了可读性差的问题,这算是protobuf的一个缺点吧)后写入一个 fstream 流。
然后在从disk读出Person的数据:
#include "person.pb.h" … void PrintInfo(const Person & person) { cout << person.name() << endl; cout << person.age() << endl; } int main(int argc, char* argv[]) { Person person; { fstream input("./log", ios::in | ios::binary); if (!person.ParseFromIstream(&input)) { cerr << "Failed to parse address book." << endl; return -1; } } PrintInfo(person); … }
主要是利用 ParseFromIstream 从一个 fstream 流中读取序列化的信息并反序列化。
下一篇将讲利用protobuf rpc进行数据传输。