Protocol Buffer技術深入理解(C++執行個體)

來源:互聯網
上載者:User

這篇Blog仍然是以Google的官方文檔為主線,代碼執行個體則完全取自於我們正在開發的一個Demo項目,通過前一段時間的嘗試,感覺這種結合的方式比較有利於培訓和內部的技術交流。還是那句話,沒有最好的,只有最適合的。我想寫Blog也是這一道理吧,不同的技術主題可能需要採用不同的風格。好了,還是讓我們儘早切入主題吧。

一、產生目標語言代碼
下面的命令協助我們將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++類的部分聲明,以及常用方法的說明性注釋。 複製代碼 代碼如下: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;
//擷取當前對象的類型名稱。
::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;
}

三、嵌套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;
}

四、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;
}

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

本打算將該Blog中使用的範例程式碼以附件的方式上傳,但是沒有發現此功能,望諒解。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.