Protocol Buffer技術詳解(C++實例)

          一、生成目標語言代碼。

      下面的命令幫助我們將MyMessage.proto文件中定義的一組Protocol Buffer格式的消息編譯成目標語言(C++)的代碼。至於消息的內容,我們會在後面以分段的形式逐一列出,同時也會在附件中給出所有源代碼。
      protoc -I=./message --cpp_out=./src ./MyMessage.proto
      從上面的命令行參數中可以看出,待編譯的文件爲MyMessage.proto,他存放在當前目錄的message子目錄下。--cpp_out參數則指示編譯工具我們需要生成目標語言是C++,輸出目錄是當前目錄的src子目錄。在本例中,生成的目標代碼文件名是MyMessage.pb.h和MyMessage.pb.cc。
    
      二、簡單message生成的C++代碼。
      這裏先定義一個最簡單的message,其中只是包含原始類型的字段。
      option optimize_for = LITE_RUNTIME;
      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類中提供了具體的實現。對於我們的項目而言,整個系統相對比較封閉,不會和更多的外部程序進行交互,與此同時,我們的客戶端部分又是運行在Android平臺,有鑑於此,我們考慮使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯後所佔用的資源也會更少,至於反射所能帶來的靈活性和極易擴展性,對於該項目而言完全可以忽略。下面我們來看一下由message LogonReqMessage生成的C++類的部分聲明,以及常用方法的說明性註釋。

複製代碼
 1     class LogonReqMessage : public ::google::protobuf::MessageLite {
 2     public:
 3         LogonReqMessage();
 4         virtual ~LogonReqMessage();
 5 
 6         // implements Message ----------------------------------------------
 7         //下面的成員函數均實現自MessageLite中的虛函數。
 8         //創建一個新的LogonReqMessage對象,等同於clone。
 9         LogonReqMessage* New() const;
10         //用另外一個LogonReqMessage對象初始化當前對象,等同於賦值操作符重載(operator=)
11         void CopyFrom(const LogonReqMessage& from);
12         //清空當前對象中的所有數據,既將所有成員變量置爲未初始化狀態。
13         void Clear();
14         //判斷當前狀態是否已經初始化。
15         bool IsInitialized() const;
16         //在給當前對象的所有變量賦值之後,獲取該對象序列化後所需要的字節數。
17         int ByteSize() const;
18         //獲取當前對象的類型名稱。
19         ::std::string GetTypeName() const;
20 
21         // required int64 acctID = 1;
22         //下面的成員函數都是因message中定義的acctID字段而生成。
23         //這個靜態成員表示AcctID的標籤值。命名規則是k + FieldName(駝峯規則) + FieldNumber。
24         static const int kAcctIDFieldNumber = 1;
25         //如果acctID字段已經被設置返回true,否則false。
26         inline bool has_acctid() const;
27         //執行該函數後has_acctid函數將返回false,而下面的acctid函數則返回acctID的缺省值。
28         inline void clear_acctid();
29         //返回acctid字段的當前值,如果沒有設置則返回int64類型的缺省值。
30         inline ::google::protobuf::int64 acctid() const;
31         //爲acctid字段設置新值,調用該函數後has_acctid函數將返回true。
32         inline void set_acctid(::google::protobuf::int64 value);
33     
34         // required string passwd = 2;
35         //下面的成員函數都是因message中定義的passwd字段而生成。這裏生成的函數和上面acctid
36         //生成的那組函數基本相似。因此這裏只是列出差異部分。
37         static const int kPasswdFieldNumber = 2;
38         inline bool has_passwd() const;
39         inline void clear_passwd();
40         inline const ::std::string& passwd() const;
41         inline void set_passwd(const ::std::string& value);
42         //對於字符串類型字段設置const char*類型的變量值。
43         inline void set_passwd(const char* value);
44         inline void set_passwd(const char* value, size_t size);
45         //可以通過返回值直接給passwd對象賦值。在調用該函數之後has_passwd將返回true。
46         inline ::std::string* mutable_passwd();
47         //釋放當前對象對passwd字段的所有權,同時返回passwd字段對象指針。調用此函數之後,passwd字段對象
48         //的所有權將移交給調用者。此後再調用has_passwd函數時將返回false。
49         inline ::std::string* release_passwd();
50     private:
51         ... ... 
52     };
複製代碼

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

複製代碼
 1     void testSimpleMessage()
 2     {
 3         printf("==================This is simple message.================\n");
 4         //序列化LogonReqMessage對象到指定的內存區域。
 5         LogonReqMessage logonReq;
 6         logonReq.set_acctid(20);
 7         logonReq.set_passwd("Hello World");
 8         //提前獲取對象序列化所佔用的空間並進行一次性分配,從而避免多次分配
 9         //而造成的性能開銷。通過該種方式,還可以將序列化後的數據進行加密。
10         //之後再進行持久化,或是發送到遠端。
11         int length = logonReq.ByteSize();
12         char* buf = new char[length];
13         logonReq.SerializeToArray(buf,length);
14         //從內存中讀取並反序列化LogonReqMessage對象,同時將結果打印出來。
15         LogonReqMessage logonReq2;
16         logonReq2.ParseFromArray(buf,length);
17         printf("acctID = %I64d, password = %s\n",logonReq2.acctid(),logonReq2.passwd().c_str());
18         delete [] buf;
19     }
複製代碼

      三、嵌套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++代碼和關鍵性註釋。

複製代碼
 1     class LogonRespMessage : public ::google::protobuf::MessageLite {
 2     public:
 3         LogonRespMessage();
 4         virtual ~LogonRespMessage();
 5     
 6         // implements Message ----------------------------------------------
 7         ... ... //這部分函數和之前的例子一樣。
 8         
 9         // required .LoginResult logonResult = 1;
10         //下面的成員函數都是因message中定義的logonResult字段而生成。
11         //這一點和前面的例子基本相同,只是類型換做了枚舉類型LoginResult。    
12         static const int kLogonResultFieldNumber = 1;
13         inline bool has_logonresult() const;
14         inline void clear_logonresult();
15         inline LoginResult logonresult() const;
16         inline void set_logonresult(LoginResult value);
17         
18         // required .UserInfo userInfo = 2;
19         //下面的成員函數都是因message中定義的UserInfo字段而生成。
20         //這裏只是列出和非消息類型字段差異的部分。
21         static const int kUserInfoFieldNumber = 2;
22         inline bool has_userinfo() const;
23         inline void clear_userinfo();
24         inline const ::UserInfo& userinfo() const;
25         //可以看到該類並沒有生成用於設置和修改userInfo字段set_userinfo函數,而是將該工作
26         //交給了下面的mutable_userinfo函數。因此每當調用函數之後,Protocol Buffer都會認爲
27         //該字段的值已經被設置了,同時has_userinfo函數亦將返回true。在實際編碼中,我們可以
28         //通過該函數返回userInfo字段的內部指針,並基於該指針完成userInfo成員變量的初始化工作。
29         inline ::UserInfo* mutable_userinfo();
30         inline ::UserInfo* release_userinfo();
31     private:
32         ... ...
33     };                    
複製代碼

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

複製代碼
 1     void testNestedMessage()
 2     {
 3         printf("==================This is nested message.================\n");
 4         LogonRespMessage logonResp;
 5         logonResp.set_logonresult(LOGON_RESULT_SUCCESS);
 6         //如上所述,通過mutable_userinfo函數返回userInfo字段的指針,之後再初始化該對象指針。
 7         UserInfo* userInfo = logonResp.mutable_userinfo();
 8         userInfo->set_acctid(200);
 9         userInfo->set_name("Tester");
10         userInfo->set_status(OFFLINE);
11         int length = logonResp.ByteSize();
12         char* buf = new char[length];
13         logonResp.SerializeToArray(buf,length);
14     
15         LogonRespMessage logonResp2;
16         logonResp2.ParseFromArray(buf,length);
17         printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"
18             ,logonResp2.logonresult(),logonResp2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status());
19         delete [] buf;
20     }    
複製代碼

      四、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字段生成的代碼進行更爲詳細的解釋。

複製代碼
 1     class RetrieveBuddiesResp : public ::google::protobuf::MessageLite {
 2     public:
 3         RetrieveBuddiesResp();
 4         virtual ~RetrieveBuddiesResp();
 5 
 6         ... ... //其餘代碼的功能性註釋均可參照前面的例子。
 7             
 8         // repeated .BuddyInfo buddiesInfo = 2;
 9         static const int kBuddiesInfoFieldNumber = 2;
10         //返回數組中成員的數量。
11         inline int buddiesinfo_size() const;
12         //清空數組中的所有已初始化成員,調用該函數後,buddiesinfo_size函數將返回0。
13         inline void clear_buddiesinfo();
14         //返回數組中指定下標所包含元素的引用。
15         inline const ::BuddyInfo& buddiesinfo(int index) const;
16         //返回數組中指定下標所包含元素的指針,通過該方式可直接修改元素的值信息。
17         inline ::BuddyInfo* mutable_buddiesinfo(int index);
18         //像數組中添加一個新元素。返回值即爲新增的元素,可直接對其進行初始化。
19         inline ::BuddyInfo* add_buddiesinfo();
20         //獲取buddiesInfo字段所表示的容器,該函數返回的容器僅用於遍歷並讀取,不能直接修改。
21         inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >&
22           buddiesinfo() const;
23         //獲取buddiesInfo字段所表示的容器指針,該函數返回的容器指針可用於遍歷和直接修改。
24         inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*
25           mutable_buddiesinfo();
26     private:
27         ... ...
28     };
複製代碼

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

複製代碼
 1     void testRepeatedMessage()
 2     {
 3         printf("==================This is repeated message.================\n");
 4         RetrieveBuddiesResp retrieveResp;
 5         retrieveResp.set_buddiescnt(2);
 6         BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();
 7         buddyInfo->set_groupid(20);
 8         UserInfo* userInfo = buddyInfo->mutable_userinfo();
 9         userInfo->set_acctid(200);
10         userInfo->set_name("user1");
11         userInfo->set_status(OFFLINE);
12     
13         buddyInfo = retrieveResp.add_buddiesinfo();
14         buddyInfo->set_groupid(21);
15         userInfo = buddyInfo->mutable_userinfo();
16         userInfo->set_acctid(201);
17         userInfo->set_name("user2");
18         userInfo->set_status(ONLINE);
19     
20         int length = retrieveResp.ByteSize();
21         char* buf = new char[length];
22         retrieveResp.SerializeToArray(buf,length);
23     
24         RetrieveBuddiesResp retrieveResp2;
25         retrieveResp2.ParseFromArray(buf,length);
26         printf("BuddiesCount = %d\n",retrieveResp2.buddiescnt());
27         printf("Repeated Size = %d\n",retrieveResp2.buddiesinfo_size());
28         //這裏僅提供了通過容器迭代器的方式遍歷數組元素的測試代碼。
29         //事實上,通過buddiesinfo_size和buddiesinfo函數亦可循環遍歷。
30         RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();
31         RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();
32         for (; it != buddiesInfo->end(); ++it) {
33             printf("BuddyInfo->groupID = %d\n", it->groupid());
34             printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"
35                 , it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());
36         }
37         delete [] buf;
38     }
複製代碼

      最後需要說明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特別是針對序列化的目的地,比如文件流和網絡流等。與此同時,也提供了完整的官方文檔和規範的命名規則,在很多情況下,可以直接通過函數的名字便可獲悉函數所完成的工作。

發佈了57 篇原創文章 · 獲贊 108 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章