這篇Blog仍然是以Google的官方文檔爲主線,代碼實例則徹底取自於咱們正在開發的一個Demo項目,經過前一段時間的嘗試,感受這種結合的方式比較有利於培訓和內部的技術交流。仍是那句話,沒有最好的,只有最適合的。我想寫Blog也是這一道理吧,不一樣的技術主題可能須要採用不一樣的風格。好了,仍是讓咱們儘早切入主題吧。
1、生成目標語言代碼。
下面的命令幫助咱們將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。
2、簡單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++類的部分聲明,以及經常使用方法的說明性註釋。html
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 }
3、嵌套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 }
4、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仍然提供了不少其它很是有用的功能,特別是針對序列化的目的地,好比文件流和網絡流等。與此同時,也提供了完整的官方文檔和規範的命名規則,在不少狀況下,能夠直接經過函數的名字即可獲悉函數所完成的工做。測試