之前玩 C,Json、XML 什么的看多了,如今开始玩 C++,才发现我了解的世界过小了——原来 C++ 届还有 Google Protocol Buffers 这么好的东西。果真在 PC 上作开发真是好,不用考虑可执行程序的 size,能够放心放肆地用 C++。html
Protocol Buffer Basics: C++
Google Protocol Buffers
Google Protocol Buffer 的使用和原理 - IBM
这是一份颇有诚意的 Protocol Buffer 语法详解linux
Protocol Buffers 又简称为 Protobuf、PB。是 Google 推出的一种数据交换格式。注意,这仍是二进制的交换数据。程序员
Protobuf 有本身的编译器,在 Linux 中叫作 protoc
,能够解释 .proto
文件而且声称对应语言的源文件。目前 Google 提供了三种语言:Java, C++, Python。后面咱们就以 C++ 来讲明,其余语言相似。函数
总结一下:咱们所说的 Protobuf,其实能够说是包含如下几部分:ui
.proto
,使用这种源文件,能够定义存储类的内容protoc
,能够编译 .proto
编译成 .cc
文件,使之成为一个能够在 C++ 工程中直接使用的类。类的功能很是完善,后文辉具体说明。咱们来定义一个能够覆盖大多数使用状况的例子,定义一个高中的班级和班上学生的信息google
// -------------------------------------- // File: School.HighSchool.proto // package School.HighSchool; message Person { optional int32 id = 1; optional int32 age = 2; optional string first_name = 3; optional string last_name = 4; optional bool is_female = 5; }; message Class { optional int32 grade_num = 1; optional int32 class_num = 2; optional Person head_teacher = 3; repeated Person students = 4; };
文件的建议命名为 “包名.消息名.proto
”。对于 C++ 而言,就是 “命名空间.数据类.proto
”。编码
上面这段的语义,我直接用 C++ 的概念来讲明吧:url
这里有必要说明 optional
和 repeated
。前者表示这个数据类型是可选的,也就是说有可能不存在这样的一个数据信息。后者表示这个数据类型是多个的,能够理解为一个堆,或者说一个 set、一个集合,总之就是多个同类数据,相似于 C++ 中的 vector
。对应于 JSON 中的 array。Repeated 类型的数据有多是空的(成员为 0)。spa
与 optional
相对应的是 required
类型,表示这个数据类型是必须的。可是,大部分资料都建议不要用这种类型。.net
上面的源文件,可使用如下命令进行编译:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR School.HighSchool.proto
编译完成后,生成两个文件:School.HighSchool.pb.cc
和 School.HighSchool.pb.h
。
在类里面,大致结构是这样的:
namespace School { namespace HighSchool { class Person : public ::google::protobuf::Message { ... } // end of class Student class Class : public ::google::protobuf::Message { ... } // end of class public }} // end of namespaces
通常而言生成的类,都有对应整个类的操做方法,经常使用的几个方法以下:
CopyFrom (...) // operator= (...) 的具体实现 MergeFrom (...) ByteSize () const Swap (...)
而最重要的两个方法则是:
bool SerializeToString (string *output) const; bool ParseFromString (const string &data);
做用分别是序列化和反序列化,也就是
string
对象中。string
类型中解析出 PB 对象对于具体的数据成员,则给出具体的 get / set
方法。好比 Person 类的 id
成员,C++ 类会提供如下方法:
inline bool has_id() const; inline void clear_id(); static const int kInt32IdNumber = 0; inline ::google::protobuf::int32 id() const; inline void set_id(::google::protobuf::int32 value);
全部的方法都按照字面意思就能够读懂,很是好理解。
而对于 repeated
属性的成员,好比 students,则比较复杂,使用了 STL:
inline int students_size() const; inline void clear_students_size(); inline const ::School::HighSchool::Person &students(int index) const; inline ::School::HighSchool::Person *mutable_students(int index); inline ::School::HighSchool::Person *add_students(); inline const ::google::protobuf::RepeatedPtrField <::School::HighSchool::Person> &students() const; inline ::google::protobuf::RepeatedPtrField <::School::HighSchool::Person> *mutable_students();
看起来比较复杂,其实仍是很好理解的。请读者结合 C++ 的迭代器理解就行了。
Protobuf 中经常使用的基本数据类型及必要的说明以下:
double
float
int32
, int64
:负数的编码效率低于 sintXX
系列。当有负数,可是出现频率不高时使用uint32
, uint64
sint32
, sint64
:当负数出现的频率比较高时,比 int32
的效率高fixed32
, fixed64
:注意,这不是 “定点数” 的意思,而是表示定长 4 字节的整形数据。若是数字长期大于 228 时,比 int32
效率更高sfixed32
, sfixed64
bool
string
:ASCII / UTF-8 字符串bytes
:二进制序列笔者在使用 pb 的过程当中遇到了一些坑(其实只是特性),这里列出来,读者在实际使用中应该留意一下:
PB 的每一个成员,不论你在 proto 文件中是怎么写的,最终都会给你转换成小写。好比定义了一个成员 optional bytes bytes_article_URL
,最终生成的 get / set 方法是 bytes_article_url()
和 set_bytes_article_url()
。可是呢,在 enum
类型里面的定义就不在此限。程序员要留意这个问题。
枚举值在 pb 中,使用很是方便。但是咱们须要注意一种状况,就是若是 pb 遇到了一个它不认识的(未定义)的枚举值,它的解决方法是直接抛出 exception
。对于 C++ 来讲,未处理的 exception 就意味着程序退出,并且不是 core,而是相似于调用 exit()
的正常退出。对于一些基于 core 的监控机制可能无效。
为了解决这个问题,有两种方法: