Google Protocol Buffer(protoc, protobuf, pb)學習筆記

之前玩 C,Json、XML 什麼的看多了,如今開始玩 C++,才發現我瞭解的世界過小了——原來 C++ 屆還有 Google Protocol Buffers 這麼好的東西。果真在 PC 上作開發真是好,不用考慮可執行程序的 size,能夠放心放肆地用 C++。html

Reference

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

  • 一種數據交換格式,能夠將 C++ 中定義的存儲類的內容 與 二進制序列串 相互轉換,主要用於數據傳輸或保存
  • 定義了一種源文件,擴展名爲 .proto,使用這種源文件,能夠定義存儲類的內容
  • Google 提供了一個編譯器 protoc,能夠編譯 .proto 編譯成 .cc 文件,使之成爲一個能夠在 C++ 工程中直接使用的類。類的功能很是完善,後文輝具體說明。

.proto 的簡單語法

咱們來定義一個能夠覆蓋大多數使用狀況的例子,定義一個高中的班級和班上學生的信息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

  1. 定義包的分類(命名空間)是 School 類別下的 HighSchool 子類別。
  2. 定義一個 我的 數據類,包含學生的 ID、姓、名、性別等等信息。
  3. 定義一個 班級 數據類,包含年級號和班級號、班主任信息、全部學生的信息。

這裏有必要說明 optionalrepeated。前者表示這個數據類型是可選的,也就是說有可能不存在這樣的一個數據信息。後者表示這個數據類型是多個的,能夠理解爲一個,或者說一個 set、一個集合,總之就是多個同類數據,相似於 C++ 中的 vector。對應於 JSON 中的 array。Repeated 類型的數據有多是空的(成員爲 0)。spa

optional 相對應的是 required 類型,表示這個數據類型是必須的。可是,大部分資料都建議不要用這種類型。.net

生成 C++ 類

上面的源文件,可使用如下命令進行編譯:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR School.HighSchool.proto

編譯完成後,生成兩個文件:School.HighSchool.pb.ccSchool.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);

做用分別是序列化和反序列化,也就是

  1. 將 PB 的內容序列化(二進制化)到指定的 string 對象中。
  2. string 類型中解析出 PB 對象

get / set 方法

對於具體的數據成員,則給出具體的 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 類型裏面的定義就不在此限。程序員要留意這個問題。

enum 值的定義

枚舉值在 pb 中,使用很是方便。但是咱們須要注意一種狀況,就是若是 pb 遇到了一個它不認識的(未定義)的枚舉值,它的解決方法是直接拋出 exception。對於 C++ 來講,未處理的 exception 就意味着程序退出,並且不是 core,而是相似於調用 exit() 的正常退出。對於一些基於 core 的監控機制可能無效。

爲了解決這個問題,有兩種方法:

  1. 每次針對 enum 類型進行 catch
  2. 不要使用 enum 類型,而是改成 uint 之類的。enum 的值能夠轉換成爲普通的 uint / int 系列類型,既有了 enum 的明確語義,對於不方便作 catch 的程序而言,而更加方便
相關文章
相關標籤/搜索