之前玩 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 的監控機制可能無效。
爲了解決這個問題,有兩種方法: