天天看點

【詳細解析版】Protobuf3使用手冊第1章 定義.proto 檔案 第2章 編譯 .proto 檔案第3章 使用message

Protobuf使用手冊--中文版,好不容易找到了,分享一下給大家。

目錄

第1章 定義.proto 檔案

1.1 定義package

1.2 定義message

1.3 定義屬性

1.3.1 标注

1.3.2 類型

1.3.3 屬性順序号

1.4 可選項

1.4.1 import可選項

1.4.2 packed

1.4.3 default

1.5 大資料量使用建議

1.5.1 repeated message類型

1.5.2 repeated raw類型

1.6 Protocol Buffer消息更新原則

第2章 編譯 .proto 檔案

第3章 使用message

3.1 類成員變量的通路

3.2 标準message方法

3.3 編碼和解碼函數

3.4 簡單message生成的C++代碼

3.5 嵌套message生成的C++代碼

3.6 repeated嵌套message生成的C++代碼

第1章 定義.proto 檔案

首先我們需要編寫一個 proto 檔案,定義我們程式中需要處理的結構化資料,在 protobuf 的術語中,結構化資料被稱為 Message。proto 檔案非常類似 java 或者 C 語言的資料定義,可以使用C或C++風格的注釋。下面是一個proto檔案的例子。

syntax = "proto3";
package tutorial;
 
 
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
 
 
message Person {
required string name = 1;
required int32 id = 2;        // Unique ID number for this person.
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;
 
}
 
 
// Our address book file is just one of these.
 
message AddressBook {
repeated Person person = 1;
 
}
           

一個proto檔案主要包含package定義、message定義和屬性定義三個部分,還有一些可選項。

檔案的第一行指定了你正在使用proto3文法:如果你沒有指定這個,編譯器會使用proto2。這個指定文法行必須是檔案的非空非注釋的第一個行。

1.1 定義package

Package在c++中對應namespace。

對于Java,包聲明符會變為java的一個包,除非在.proto檔案中提供了一個明确有java_package。

1.2 定義message

Message在C++中對應class。Message中定義的全部屬性在class中全部為private的。

Message的嵌套使用可以嵌套定義,也可以采用先定義再使用的方式。

Message的定義末尾可以采用java方式在不加“;”,也可以采用C++定義方式在末尾加上“;”,這兩種方式都相容,建議采用java定義方式。

向.proto檔案添加注釋,可以使用C/C++/java風格的雙斜杠(//) 文法格式。

1.3 定義屬性

屬性定義分為四部分:标注+類型+屬性名+屬性順序号+[預設值],其示意如下所示。

标注 類型 屬性名 屬性順序号 [預設值]
required string name = 1 [default=””];

       其中屬性名與C++和java語言類似,不再解釋;下面分别對标注、類型和屬性順序号加以詳細介紹。

其中包名和消息名以及其中變量名均采用java的命名規則——駝峰式命名法,駝峰式命名法規則見附件1。

1.3.1 标注

标注包括“required”、“optional”、“repeated”三種,其中

required表示該屬性為必選屬性,否則對應的message“未初始化”,debug模式下導緻斷言,release模式下解析失敗;

optional表示該屬性為可選屬性,不指定,使用預設值(int或者char資料類型預設為0,string預設為空,bool預設為false,嵌套message預設為構造,枚舉則為第一個)

repeated表示該屬性為重複字段,可看作是動态數組,類似于C++中的vector。

如果為optional屬性,發送端沒有包含該屬性,則接收端在解析式采用預設值。對于預設值,如果已設定預設值,則采用預設值,如果未設定,則類型特定的預設值為使用,例如string的預設值為””。

1.3.2 類型

Protobuf的屬性基本包含了c++需要的所有基本屬性類型。

protobuf屬性 C++屬性 java屬性 備注
double double double 固定8個位元組
float float float 固定4個位元組
int32 int32 int32 使用變長編碼,對于負數編碼效率較低,如果經常使用負數,建議使用sint32
int64 int64 int64 使用變長編碼,對于負數編碼效率較低,如果經常使用負數,建議使用sint64
uint32 uint32 int 使用變長編碼
uint64 uint64 long 使用變長編碼
sint32 int32 int 采用zigzag壓縮,對負數編碼效率比int32高
sint64 int64 long 采用zigzag壓縮,對負數編碼效率比int64高
fixed32 uint32 int 總是4位元組,如果資料>2^28,編碼效率高于unit32
fixed64 uint64 long 總是8位元組,如果資料>2^56,編碼效率高于unit32
sfixed32 int32 int 總是4位元組
sfixed64 int64 long 總是8位元組
bool bool boolean
string string String 一個字元串必須是utf-8編碼或者7-bit的ascii編碼的文本
bytes string ByteString 可能包含任意順序的位元組資料

1.3.2.1 Union類型定義

Protobuf沒有提供union類型,如果希望使用union類型,可以采用enum和optional屬性定義的方式。

例如,如果已經定義了Foo、Bar、Baz等message,則可以采用如下定義。

message OneMessage {
 
  enum Type { FOO = 1; BAR = 2; BAZ = 3; }
 
  // 辨別要填寫的字段,必填
  required Type type = 1;
 
  // 将填寫以下内容之一,可選
  optional Foo foo = 2;
  optional Bar bar = 3;
  optional Baz baz = 4;
 
}
           

1.3.3 屬性順序号

屬性順序号是protobuf為了提高資料的壓縮和可選性等功能定義的,需要按照順序進行定義,且不允許有重複。

1.4 可選項

1.4.1 import可選項

Import可選項用于包含其它proto檔案中定義的message或enum類型等。标準格式如下

import “phonetype.proto”;

使用時,import的檔案必須與目前檔案處于同一個檔案夾下,protoc無法完成不處于同一個檔案夾下的import選項。

1.4.2 packed

packed (field option): 如果該選項在一個整型基本類型上被設定為真,則采用更緊湊的編碼方式。當然使用該值并不會對數值造成任何損失。在2.3.0版本之前,解析器将會忽略那些 非期望的包裝值。是以,它不可能在不破壞現有架構的相容性上而改變壓縮格式。在2.3.0之後,這種改變将是安全的,解析器能夠接受上述兩種格式,但是在 處理protobuf老版本程式時,還是要多留意一下。

repeated int32 samples = 4 [packed=true];

1.4.3 default

[default = default_value]: optional類型的字段,如果在序列化時沒有被設定,或者是老版本的消息中根本不存在該字段,那麼在反序列化該類型的消息是,optional的字段将被賦予類型相關的預設值,如bool被設定為false,int32被設定為0。Protocol Buffer也支援自定義的預設值,如:

  optional int32 result_per_page = 3 [default = 10]。

1.5 大資料量使用建議

在使用過程中發現,對于大資料量的協定封包(循環超過10000條),如果repeated修飾的屬性為對象類型(諸如message 、Bytes、string等稱為“對象類型”,其餘的諸如int32、int64、float等等稱為“原始類型”)時,效率非常低,而且占用的程序記憶體也非常大,建議采用如下方式優化。

1.5.1 repeated message類型

在message 中對repeated 辨別的 message類型的字段需要做大量ADD操作時,可以考慮盡量避免嵌套message或者減少嵌套的message個數。

1.5.2 repeated raw類型

在message中對repeated 辨別的原始資料類型的字段需要做大量ADD操作(例如超過3千)時,可以考慮預配置設定資料空間,避免重複大量地配置設定空間。

1.5.3 repeated Bytes類型

在protobuf中,Bytes基于C++ STL中的string實作,因為string記憶體管理的原因,程式空間往往較大。是以應用如果有很多repeated Bytes類型的字段的話,程序顯示耗用大量記憶體,這與vector<string>的情況基本一緻。

1.6 Protocol Buffer消息更新原則

      在實際的開發中會存在這樣一種應用場景,既消息格式因為某些需求的變化而不得不進行必要的更新,但是有些使用原有消息格式的應用程式暫時又不能被立刻更新,這便要求我們在更新消息格式時要遵守一定的規則,進而可以保證基于新老消息格式的新老程式同時運作。規則如下:

      1. 不要修改已經存在字段的标簽号。

      2. 任何新添加的字段必須是optional和repeated限定符,否則無法保證新老程式在互相傳遞消息時的消息相容性。

      3. 在原有的消息中,不能移除已經存在的required字段,optional和repeated類型的字段可以被移除,但是他們之前使用的标簽号必須被保留,不能被新的字段重用。

      4. int32、uint32、int64、uint64和bool等類型之間是相容的,sint32和sint64是相容的,string和bytes是相容的,fixed32和sfixed32,以及fixed64和sfixed64之間是相容的,這意味着如果想修改原有字段的類型時,為了保證相容性,隻能将其修改為與其原有類型相容的類型,否則就将打破新老消息格式的相容性。

      5. optional和repeated限定符也是互相相容的。

第2章 編譯 .proto 檔案

可以通過定義好的.proto檔案來生成Java、Python、C++代碼,需要基于.proto檔案運作protocol buffer編譯器protoc。如果你沒有安裝編譯器,下載下傳安裝包并遵照README安裝。運作的指令如下所示:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

MPORT_PATH聲明了一個.proto檔案所在的具體目錄。如果忽略該值,則使用目前目錄。如果有多個目錄則可以 對--proto_path 寫多次,它們将會順序的被通路并執行導入。-I=IMPORT_PATH是它的簡化形式。

當然也可以提供一個或多個輸出路徑:

  • --cpp_out

     在目标目錄DST_DIR中産生C++代碼,可以在C++代碼生成參考中檢視更多。
  • --java_out

     在目标目錄DST_DIR中産生Java代碼,可以在 Java代碼生成參考中檢視更多。
  • --python_out

     在目标目錄 DST_DIR 中産生Python代碼,可以在Python代碼生成參考中檢視更多。
  • --go_out

     在目标目錄 DST_DIR 中産生Go代碼,可以在GO代碼生成參考中檢視更多。
  • --ruby_out

    在目标目錄 DST_DIR 中産生Go代碼,參考正在制作中。
  • --javanano_out

    在目标目錄DST_DIR中生成JavaNano,JavaNano代碼生成器有一系列的選項用于定制自定義生成器的輸出:你可以通過生成器的README查找更多資訊,JavaNano參考正在制作中。
  • --objc_out

    在目标目錄DST_DIR中産生Object代碼,可以在Objective-C代碼生成參考中檢視更多。
  • --csharp_out

    在目标目錄DST_DIR中産生Object代碼,可以在C#代碼生成參考中檢視更多。
  • --php_out

    在目标目錄DST_DIR中産生Object代碼,可以在PHP代碼生成參考中檢視更多。

作為一個友善的拓展,如果DST_DIR以.zip或者.jar結尾,編譯器會将輸出寫到一個ZIP格式檔案或者符合JAR标準的.jar檔案中。注意如果輸出已經存在則會被覆寫,編譯器還沒有智能到可以追加檔案。

- 你必須提議一個或多個.proto檔案作為輸入,多個.proto檔案可以隻指定一次。雖然檔案路徑是相對于目前目錄的,每個檔案必須位于其IMPORT_PATH下,以便每個檔案可以确定其規範的名稱。

第3章 使用message

3.1 類成員變量的通路

在生成的.h檔案中定義了類成員的通路方法。例如,對于Person類,定義了name、id、email、phone等成員的通路方法。

擷取成員變量值直接采用使用成員變量名(全部為小寫),設定成員變量值,使用在成員變量名前加set_的方法。

對于普通成員變量(required和optional)提供has_方法判斷變量值是否被設定;提供clear_方法清除設定的變量值。

對于string類型,提供多種set_方法,其參數不同。同時,提供了一個mutable_方法,傳回變量值的可修改指針。

對于repeated變量,提供了其它一些特殊的方法:

l   _size方法:傳回repeated field’s

l   通過下腳标通路其中的數組成員組

l   通過下腳标傳回其中的成員的mutable_的方法

l   _add方法:增加一個成員。

3.2 标準message方法

生成的.h檔案中的class都繼承自::google::protobuf::Message類,Message類提供了一些方法可以檢查或者操作整個message,包括

l   

bool IsInitialized() const;檢查是否所有required變量都已經初始化;      

l   

string DebugString() const;傳回message的可閱讀的表示,主要用于調試程式;

l   

void CopyFrom(const Person& from);使用一個message的值覆寫本message;

l   

void Clear();

清空message的所有成員變量值。

3.3 編碼和解碼函數

每個message類都提供了寫入和讀取message資料的方法,包括

l   

bool SerializeToString(string* output) const;把message編碼進output。 

l   

bool ParseFromString(const string& data);從string解碼到message

l   

bool SerializeToArray(char* buf,int size) const;把message編碼進數組buf.

l   

bool ParseFromArray(const char* buf,int size);把buf解碼到message

。此解碼方法效率較

ParseFromString

高很多,是以一般用這種方法解碼。

l   

bool SerializeToOstream(ostream* output) const;

把message編碼進ostream

l   

bool ParseFromIstream(istream* input);

從istream解碼到message

備注:發送接收端所使用的加碼解碼方法不一定非得配對,即發送端用SerializeToString 接收端不一定非得用ParseFromString ,可以使用其他解碼方法。

3.4 簡單message生成的C++代碼

這裡先定義一個最簡單的message,其中隻是包含原始類型的字段。

message LogonReqMessage {
    required int64 acctID = 1;
    required string passwd = 2;
}
           

由于我們在MyMessage檔案中定義選項optimize_for的值為LITE_RUNTIME,是以由該.proto檔案生成的所有C++類的父類均為::google::protobuf::MessageLite,而非::google::protobuf::Message。MessageLite類是Message的父類,在MessageLite中将缺少Protocol Buffer對反射的支援,而此類功能均在Message類中提供了具體的實作。使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯後所占用的資源也會更少,至于反射所能帶來的靈活性和極易擴充性。下面我們來看一下由message LogonReqMessage生成的C++類的部分聲明,以及常用方法的說明性注釋。

class LogonReqMessage : public ::google::protobuf::MessageLite {
 
    public:
        LogonReqMessage();
        virtual ~LogonReqMessage();
 
 
        // implements Message ----------------------------------------------
        //下面的成員函數均實作自MessageLite中的虛函數。
        //建立一個新的LogonReqMessage對象,等同于clone。
        LogonReqMessage* New() const;
        //用另外一個LogonReqMessage對象初始化目前對象,等同于指派操作符重載(operator=)
        void CopyFrom(const LogonReqMessage& from);
        //清空目前對象中的所有資料,既将所有成員變量置為未初始化狀态。
        void Clear();
        //判斷目前狀态是否已經初始化。
        bool IsInitialized() const;
        //在給目前對象的所有變量指派之後,擷取該對象序列化後所需要的位元組數。
        int ByteSize() const;
        //擷取目前對象的類型名稱。
        inline ::std::string GetTypeName() const;
 
 
 
        // required int64 acctID = 1;
        //下面的成員函數都是因message中定義的acctID字段而生成。
        //這個靜态成員表示AcctID的标簽值。命名規則是k + FieldName(駝峰規則) + FieldNumber。
        static const int kAcctIDFieldNumber = 1;
        //如果acctID字段已經被設定傳回true,否則false。
        inline bool has_acctid() const;
        //執行該函數後has_acctid函數将傳回false,而下面的acctid函數則傳回acctID的預設值。
        inline void clear_acctid();
        //傳回acctid字段的目前值,如果沒有設定則傳回int64類型的預設值。
        inline ::google::protobuf::int64 acctid() const;
        //為acctid字段設定新值,調用該函數後has_acctid函數将傳回true。
        inline void set_acctid(::google::protobuf::int64 value);
 
   
 
        // required string passwd = 2;
        //下面的成員函數都是因message中定義的passwd字段而生成。這裡生成的函數和上面acctid
        //生成的那組函數基本相似。是以這裡隻是列出差異部分。
        static const int kPasswdFieldNumber = 2;
        inline bool has_passwd() const;
        inline void clear_passwd();
        inline const ::std::string& passwd() const;
        inline void set_passwd(const ::std::string& value);
        //對于字元串類型字段設定const char*類型的變量值。
        inline void set_passwd(const char* value);
        inline void set_passwd(const char* value, size_t size);
        //可以通過傳回值直接給passwd對象指派。在調用該函數之後has_passwd将傳回true。
        inline ::std::string* mutable_passwd();
        //釋放目前對象對passwd字段的所有權,同時傳回passwd字段對象指針。調用此函數之後,passwd字段對象
 
        //的所有權将移交給調用者。此後再調用has_passwd函數時将傳回false。
        inline ::std::string* release_passwd();
 
    private:
 
        //... ...
 
    };
           

下面是讀寫LogonReqMessage對象的C++測試代碼和說明性注釋。

void testSimpleMessage()
 
    {
 
        printf("==================This is simple message.================\n");
        //序列化LogonReqMessage對象到指定的記憶體區域。
        LogonReqMessage logonReq;
        logonReq.set_acctid(20);
        logonReq.set_passwd("Hello World");
        //提前擷取對象序列化所占用的空間并進行一次性配置設定,進而避免多次配置設定
        //而造成的性能開銷。通過該種方式,還可以将序列化後的資料進行加密。
        //之後再進行持久化,或是發送到遠端。
        int length = logonReq.ByteSize();
        char* buf = new char[length];
        logonReq.SerializeToArray(buf,length);
        //從記憶體中讀取并反序列化LogonReqMessage對象,同時将結果列印出來。
        LogonReqMessage logonReq2;
        logonReq2.ParseFromArray(buf,length);
        printf("acctID = %I64d, password = %s\n",logonReq2.acctid(),logonReq2.passwd().c_str());
 
        delete [] buf;
 
    }
           

3.5 嵌套message生成的C++代碼

enum UserStatus {

          OFFLINE = 0;

          ONLINE = 1;

      }

      enum LoginResult {

          LOGON_RESULT_SUCCESS = 0;

          LOGON_RESULT_NOTEXIST = 1;

          LOGON_RESULT_ERROR_PASSWD = 2;

          LOGON_RESULT_ALREADY_LOGON = 3;

          LOGON_RESULT_SERVER_ERROR = 4;

      }

      message UserInfo {

          required int64 acctID = 1;

          required string name = 2;

          required UserStatus status = 3;

      }

      message LogonRespMessage {

          required LoginResult logonResult = 1;

          required UserInfo userInfo = 2; //這裡嵌套了UserInfo消息。

      }
           

對于上述消息生成的C++代碼,UserInfo因為隻是包含了原始類型字段,是以和上例中的LogonReqMessage沒有太多的差别,這裡也就不在重複列出了。由于LogonRespMessage消息中嵌套了UserInfo類型的字段,在這裡我們将僅僅給出該消息生成的C++代碼和關鍵性注釋。

class LogonRespMessage : public ::google::protobuf::MessageLite {

    public:

        LogonRespMessage();

        virtual ~LogonRespMessage();
 
        // implements Message 

        //這部分函數和之前的例子一樣。  

        // required .LoginResult logonResult = 1;

        //下面的成員函數都是因message中定義的logonResult字段而生成。

        //這一點和前面的例子基本相同,隻是類型換做了枚舉類型LoginResult。   

        static const int kLogonResultFieldNumber = 1;

        inline bool has_logonresult() const;

        inline void clear_logonresult();

        inline LoginResult logonresult() const;

        inline void set_logonresult(LoginResult value);  

        // required .UserInfo userInfo = 2;

        //下面的成員函數都是因message中定義的UserInfo字段而生成。

        //這裡隻是列出和非消息類型字段差異的部分。

        static const int kUserInfoFieldNumber = 2;

        inline bool has_userinfo() const;

        inline void clear_userinfo();

        inline const ::UserInfo& userinfo() const;

        //可以看到該類并沒有生成用于設定和修改userInfo字段set_userinfo函數,而是将該工作

        //交給了下面的mutable_userinfo函數。是以每當調用函數之後,Protocol Buffer都會認為

        //該字段的值已經被設定了,同時has_userinfo函數亦将傳回true。在實際編碼中,我們可以

        //通過該函數傳回userInfo字段的内部指針,并基于該指針完成userInfo成員變量的初始化工作。

        inline ::UserInfo* mutable_userinfo();

        inline ::UserInfo* release_userinfo();

    private:

        //... ...

    };
           

下面是讀寫LogonRespMessage對象的C++測試代碼和說明性注釋。

void testNestedMessage()

    {

        printf("==================This is nested message.================\n");

        LogonRespMessage logonResp;

        logonResp.set_logonresult(LOGON_RESULT_SUCCESS);

        //如上所述,通過mutable_userinfo函數傳回userInfo字段的指針,之後再初始化該對象指針。

        UserInfo* userInfo = logonResp.mutable_userinfo();

        userInfo->set_acctid(200);

        userInfo->set_name("Tester");

        userInfo->set_status(OFFLINE);

        int length = logonResp.ByteSize();

        char* buf = new char[length];

        logonResp.SerializeToArray(buf,length);

        LogonRespMessage logonResp2;

        logonResp2.ParseFromArray(buf,length);

        printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"

            ,logonResp2.logonresult(),logonResp2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status());

        delete [] buf;

    }
           

3.6 repeated嵌套message生成的C++代碼

message BuddyInfo {

          required UserInfo userInfo = 1;

          required int32 groupID = 2;

      }

      message RetrieveBuddiesResp {

          required int32 buddiesCnt = 1;

          repeated BuddyInfo buddiesInfo = 2;

      }
           

對于上述消息生成的代碼,我們将隻是針對RetrieveBuddiesResp消息所對應的C++代碼進行詳細說明,其餘部分和前面小節的例子基本相同,可直接參照。而對于RetrieveBuddiesResp類中的代碼,我們也僅僅是對buddiesInfo字段生成的代碼進行更為詳細的解釋。

class RetrieveBuddiesResp : public ::google::protobuf::MessageLite {

    public:

        RetrieveBuddiesResp();

        virtual ~RetrieveBuddiesResp();

        //其餘代碼的功能性注釋均可參照前面的例子。
    
        // repeated .BuddyInfo buddiesInfo = 2;

        static const int kBuddiesInfoFieldNumber = 2;

        //傳回數組中成員的數量。

        inline int buddiesinfo_size() const;

        //清空數組中的所有已初始化成員,調用該函數後,buddiesinfo_size函數将傳回0。

        inline void clear_buddiesinfo();

        //傳回數組中指定下标所包含元素的引用。

        inline const ::BuddyInfo& buddiesinfo(int index) const;

        //傳回數組中指定下标所包含元素的指針,通過該方式可直接修改元素的值資訊。

        inline ::BuddyInfo* mutable_buddiesinfo(int index);

        //像數組中添加一個新元素。傳回值即為新增的元素,可直接對其進行初始化。

        inline ::BuddyInfo* add_buddiesinfo();

        //擷取buddiesInfo字段所表示的容器,該函數傳回的容器僅用于周遊并讀取,不能直接修改。

        inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >&

          buddiesinfo() const;

        //擷取buddiesInfo字段所表示的容器指針,該函數傳回的容器指針可用于周遊和直接修改。

        inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*

          mutable_buddiesinfo();

    private:

        //... ...

    };
           

下面是讀寫RetrieveBuddiesResp對象的C++測試代碼和說明性注釋。

void testRepeatedMessage()

    {

        printf("==================This is repeated message.================\n");

        RetrieveBuddiesResp retrieveResp;

        retrieveResp.set_buddiescnt(2);

        BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();

        buddyInfo->set_groupid(20);

        UserInfo* userInfo = buddyInfo->mutable_userinfo();

        userInfo->set_acctid(200);

        userInfo->set_name("user1");

        userInfo->set_status(OFFLINE);

   

        buddyInfo = retrieveResp.add_buddiesinfo();

        buddyInfo->set_groupid(21);

        userInfo = buddyInfo->mutable_userinfo();

        userInfo->set_acctid(201);

        userInfo->set_name("user2");

        userInfo->set_status(ONLINE);

   

        int length = retrieveResp.ByteSize();

        char* buf = new char[length];

        retrieveResp.SerializeToArray(buf,length);

   

        RetrieveBuddiesResp retrieveResp2;

        retrieveResp2.ParseFromArray(buf,length);

        printf("BuddiesCount = %d\n",retrieveResp2.buddiescnt());

        printf("Repeated Size = %d\n",retrieveResp2.buddiesinfo_size());

        //這裡僅提供了通過容器疊代器的方式周遊數組元素的測試代碼。

        //事實上,通過buddiesinfo_size和buddiesinfo函數亦可循環周遊。

        RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();

        RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();

        for (; it != buddiesInfo->end(); ++it) {

            printf("BuddyInfo->groupID = %d\n", it->groupid());

            printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"

                , it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());

        }

        delete [] buf;

    }
           

繼續閱讀