Protobuf使用摘要和demo

作爲Google出品的開源項目,其性能和效率是非常出衆的,只是支持的語言不多,普及率沒有thrift、JSon等廣,但我相信衆人拾柴火焰高,其使用率肯定會越來越高的。使用Protobuf時間不長,僅僅摘記普遍的流程方法,供大家參考,也方便自己溫故。

1.protoc協議編寫

package com.MyCompany;    //包

message Person
{
    required string name = 1;
    required sint32  age  = 2;
    required string home = 3;
    enum HOBBY
    {
        RUNNING = 0;
        BADMINTION = 1;
        PINGPONG = 3;
    }

    optional HOBBY hobby = 4[default = RUNNING];
}

message Company
{
    repeated Person staff = 1[packed=true];
    required Person boss = 2;
    enum FIELD
    {
        IT = 0;
        MECHINE = 1;
        SECURITY = 2;
    }

    required FIELD field = 3;
    required fixed32 staff_num = 4;
    optional fixed32 rank = 5[default = 1];
    repeated uint32 vecInt = 6;

}

protobuf的一些數據類型就不說了,大家可以參考別的一些資料。
從上面的例子中,大家可以知道protobuf是支持嵌套類型的,protoc2需要在聲明成員前標記修飾符,但在protoc3中除了repeated,optional和required是不需要的,以下僅就protoc2討論:

  • required:一個格式良好的消息一定要含有1個這種字段。表示該值是必須要設置的;

  • optional:消息格式中該字段可以有0個或1個值(不超過1個)。

  • repeated:在一個格式良好的消息中,這種字段可以重複任意多次(包括0次)。重複的值的順序會被保留。表示該值可以重複,相當於java和C++中的List。

    注意:變量後的賦值 如 required FIELD field = 2;這個不是對成員field 進行賦值,只是作爲變量的位置標記,具體的成員賦值是在實例化後進行的,但修飾符爲optional時,可以設置默認值,如:
    optional fixed32 rank = 4[default = 1];

分配標籤:每個field都是唯一數字的標記,這是用來標記這個field在message二進制格式中的位置的,一旦使用就不能再修改順序了。
注:標記從1-15只有一個字節編碼,包括自增長屬性(更多的見Protocol Buffer Encoding)標記從16-2047佔用兩個字節。因此儘量頻繁使用1-15,記住爲未來的擴展留下一些位置。最小的tag你可以定義爲1,最大2的29次方-1 :536870922.你同樣不能使用19000-19999(這個位置已經被GPB自己實現)。

指定field規則:由於歷史原因,repeated字段如果是基本數字類型的話,不能有效地編碼。現在代碼可以使用特殊選項[packed=true]來得到更有效率的編碼。

注: 由於required是永遠的,應該非常慎重地給message某個字段設置爲required。如果未來你希望停止寫入或者輸出某個required字段,那就會成爲問題;因爲舊的reader將以爲沒有這個字段無法初始化message,會丟掉這部分信息。一些來自google的工程師們指出使用required弊大於利,儘量使用optional和repeated。

2.編譯
寫好.proto文件後,需要用protoc命令將其進行

protoc -I=輸入目錄  - -cpp_out=輸出目錄   yourprotobuf.proto

如果yourprotobuf.proto在當前目錄下,且編譯到當前目錄,則命令爲:

protoc -I=. --cpp_out=. yourprotobuf.proto

生成兩個文件yourprotobuf.pb.cc和yourprotobuf.pb.h。
我麼只需#include “yourprotobuf.pb.h”就行。

3.序列化/反序列化
先附上代碼:

#include "yourprotobuf.pb.h"
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/pool/pool.hpp>
using namespace com::MyCompany;
using namespace std;
using namespace boost;

int main(int argc, char *argv[])
{
    pool<> pl(sizeof(char) * 100);

    char *pTo = static_cast<char *>(pl.malloc());
 //   char *pFrom = static_cast<char *>(pl.malloc());

    Company company;
    Person *person = company.add_staff();
    person->set_age(27);
    person->set_name("Kasen");
    person->set_home("NB");
    person->set_hobby(Person_HOBBY_BADMINTION);

    Person *person1 = company.add_staff();
    person1->set_age(27);
    person1->set_name("wangzhijie");
    person1->set_home("shandong");
    person1->set_hobby(Person_HOBBY_BADMINTION);

    Person *boss = new Person();
    boss->set_age(50);
    boss->set_name("i_do_not_know");
    boss->set_home("china");
    boss->set_hobby(Person_HOBBY_BADMINTION);
    company.set_allocated_boss(boss);

    company.set_staff_num(10000);
    company.set_field(Company_FIELD_SECURITY);
    company.set_rank(1);
    for(int i = 0; i < 5; ++i)
        company.add_vecint(5);

    company.set_vecint(4,9);

    int iNeedSize = company.ByteSize();
    company.SerializeToArray(pTo,iNeedSize);

    Company company1;
    company1.ParseFromArray(pTo,iNeedSize);
    cout<<company1.staff(0).name()<<endl
        <<"staff number:"<<company1.staff_size()<<endl;

    return 1;
}

我們需要注意一下幾點:

3.1 命令空間
如果沒有

using namespace com::MyCompany;

則對Person和Company的實例化變爲:

com::hikvision::Person person
com::hikvision::Company company

3.2 repeated和required修飾符成員賦值
如果成員是類類型的,如上述代碼中,staff和boss都是Person類型的,用repeated和required兩種修飾符進行修飾時,賦值方式不一樣。
repeated:

Person *person = company.add_staff();

已經將對象person裝入company對象的list< Person >容器中了。
required:

    Person boss;
    .......
    company.set_allocated_boss(&boss);

生成一個Person類對象後,賦值完後再添加到對象company中.
####*3.3 類大小*
對類對象(修飾符爲required或者optional)的賦值有兩種方法,如在以上代碼中,對成員boss的賦值除了以上的方法外,還有一種:

    Person *boss = company.mutable_boss();
    boss->set_age(50);
    boss->set_name("i_do_not_know");
    boss->set_home("china");
    boss->set_hobby(Person_HOBBY_BADMINTION);

切記不可如下:

    Person boss;
    boss.set_age(50);
    boss.set_name("i_do_not_know");
    boss.set_home("china");
    boss.set_hobby(Person_HOBBY_BADMINTION);
    company.set_allocated_boss(&boss);

這樣boss爲局部變量,函數返回時就釋放。

####*3.4 類大小*
在用接口SerializeToArray()進行序列化時,需要輸入參數類大小,從.cc文件裏我們可以看到每個類都有接口ByteSize(),可以知道對象company實際大小,而函數sizeof()只能計算類的固定大小,而不會計算對象實際用了多大的內存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章