1. 程式人生 > >protocol buffer 使用

protocol buffer 使用

1. protocolbuffer(以下簡稱PB)是google 的一種資料交換的格式,它獨立於語言,獨立於平臺。google 提供了多種語言的實現:java、c#、c++、go 和 python,每一種實現都包含了相應語言的編譯器以及庫檔案。由於它是一種二進位制的格式,比使用xml進行資料交換快許多。可以把它用於分散式應用之間的資料通訊或者異構環境下的資料交換。作為一種效率和相容性都很優秀的二進位制資料傳輸格式,可以用於諸如網路傳輸、配置檔案、資料儲存等諸多領域。

3. 首先,你需要通過在.proto檔案中定義protocol buffer的message型別來指定你想要序列化的資料結構,每一個protocol buffer message是一個邏輯上的資訊記錄,它包含一系列的鍵值對。這裡展示一個最基本的.ptoto檔案的例子,它定義了一個包含Student資訊的message:

message Student{

  required string name = 1;

  required int32 id = 2;

  optional string email = 3;

  enum PhoneType {

   MOBILE = 0;

   HOME = 1;

   WORK = 2;

  }

  message PhoneNumber {

    required string number = 1;

    optional PhoneType type = 2 [default = HOME];

  }

  repeated PhoneNumber phone = 4;

}

    message的格式非常簡單--每一個message型別都有一個或多個帶有唯一編號的欄位,每一個欄位有一個欄位名和一個欄位型別,欄位型別可以是數值型別(比如整形或浮點型)、booleans(布林型別)、strings(字串型別)、raw bytes、甚至還可以是其他的protocol buffer message型別,這允許你可以分層次的組織你的資料結構。你可以單獨指定每一個欄位為optional fields(可選欄位)、required fields(必須欄位)、repeated fields(可重複欄位)。

    一旦定義了你的message,你就可以根據你所使用的語言(如JAVA、C++、Python等)使用protocol buffer提供的編譯工具編譯.proto檔案生成資料訪問類。這些類為每一個欄位都提供了簡單的訪問器(比如name()和set_name()),同時還提供了將整個結構化資料序列化為原始位元組資料以及從原始位元組資料反序列化為結構化資料的方法(譯註:C++中稱之為函式)。例如,如果你使用的語言是C++,執行編譯器編譯上述的例子將生成一個名為Student的類,在你的應用程式中你可以使用這個類來填充、序列化和反序列化Student protocol buffer messages。之後你可能會寫下如下類似的程式碼(譯註:序列化):

Student student;

student.set_name("Zhang san");

student.set_id(111);

student.set_email("[email protected]");

fstream output("file", ios::out | ios::binary);

student.SerializeToOstream(&output);

    之後,你可以將你的message讀回(反序列化):

fstream input("file", ios::in | ios::binary);

Student student;

student.ParseFromIstream(&input);

cout << "Name: " << student.name() << endl;

cout << "E-mail: " << student.email() << endl;

    你可以向你的message中新增新的欄位而不會破壞前向相容性;在解析時舊的二進位制檔案會簡單的忽略掉新欄位,所以,如果你的通訊協議中使用protocol buffers作為資料交換格式,那麼你可以擴充套件你的協議而不用擔心會打亂現有的程式碼。

4.語法格式

    1)// Filename: student.proto 這一行是註釋,語法類似於C++

    2)syntax="proto2"; 表明使用protobuf的編譯器版本為v2,目前最新的版本為v3

    3)package student; 聲明瞭一個包名,用來防止不同的訊息型別命名衝突,類似於 namespace 

    4)import "src/student.proto";  匯入了一個外部proto檔案中的定義,類似於C++中的 include

    5)message 是Protobuf中的結構化資料,類似於C++中的類,可以在其中定義需要處理的資料

    6)required string name = 1; 聲明瞭一個名為name,資料型別為string的required欄位,欄位的標識號為1

    7)protobuf一共有三個欄位修飾符:

    - required:該值是必須要設定的;

    - optional :該欄位可以有0個或1個值(不超過1個);

    - repeated:該欄位可以重複任意多次(包括0次),類似於C++中的list

    8)string是一種標量型別,protobuf的所有標量型別請參考文末的標量型別列表,name是欄位名,1 是欄位的標識號,在訊息定義中,每個欄位都有唯一的一個數字標識號,這些標識號是用來在訊息的二進位制格式中識別各個欄位的,一旦開始使用就不能夠再改變。

    9)Student內部聲明瞭一個enum和一個message,這類似於C++中的類內宣告,Student外部的結構可以用 Student.PhoneType 的方式來使用PhoneType。當使用外部package中的結構時,要使用 pkgName.msgName.typeName 的格式,每兩層之間使用'.'來連線,類似C++中的"::"。

    10)optional PhoneType type = 2 [default = HOME]; 為type欄位指定了一個預設值,當沒有為type設值時,其值為HOME。  

5. 生成C++檔案 
    protoc是proto檔案的編譯器,目前可以將proto檔案編譯成C++、Java、Python三種程式碼檔案,命令格式如下: 

    protoc -I=$SRC_DIR --cpp_out=$DST_DIR /path/to/file.proto

    上面的命令會生成xxx.pb.h 和xxx.pb.cc兩個C++檔案。

6.常用API

    protoc為message的每個required欄位和optional欄位都定義了以下幾個函式(不限於這幾個):

TypeName xxx() const;          //獲取欄位的值

bool has_xxx();              //判斷是否設值

void set_xxx(const TypeName&);   //設值

void clear_xxx();          //使其變為預設值

    為每個repeated欄位定義了以下幾個:

TypeName* add_xxx();        //增加結點

TypeName xxx(int) const;    //獲取指定序號的結點,類似於C++的"[]"運算子

TypeName* mutable_xxx(int); //類似於上一個,但是獲取的是指標

int xxx_size();            //獲取結點的數量

    另外,下面幾個是常用的序列化函式:

bool SerializeToOstream(std::ostream * output) const; //輸出到輸出流中

bool SerializeToString(string * output) const;        //輸出到string

bool SerializeToArray(void * data, int size) const;   //輸出到位元組流

    與之對應的反序列化函式:

bool ParseFromIstream(std::istream * input);     //從輸入流解析

bool ParseFromString(const string & data);       //從string解析

bool ParseFromArray(const void * data, int size); //從位元組流解析