C++?Protobuf的學習使用指南
簡介
protocol buffers 是一種語言無關、平臺無關、可擴展的序列化結構數(shù)據(jù)的方法,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲等。通信時所傳遞的信息是通過Protobuf定義的message數(shù)據(jù)結構進行打包,然后編譯成二進制的碼流再進行傳輸或者存儲。
Protocol buffers 是一種靈活,高效,自動化機制的結構數(shù)據(jù)序列化方法-可類比 XML,但是比 XML 更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡單。
Protobuf 使用的時候必須寫一個 IDL(Interface description language)文件,在里面定義好數(shù)據(jù)結構,只有預先定義了的數(shù)據(jù)結構,才能被序列化和反序列化。其中,序列化是將對象轉換二進制數(shù)據(jù),反序列化是將二進制數(shù)據(jù)轉換成對象。
教程
本教程提供了一個基本的C++程序員介紹使用協(xié)議緩沖區(qū)。通過創(chuàng)建一個簡單的示例應用程序,它向您展示了如何
- 在.proto文件中定義消息格式。
- 使用協(xié)議緩沖區(qū)編譯器。
- 使用C++協(xié)議緩沖區(qū)API寫入和讀取消息。
這不是一個在C++中使用協(xié)議緩沖區(qū)的全面指南。有關更多詳細的參考信息,請參見Protocol Buffer Language Guide(proto2)、Protocol Buffer Language Guide(proto3)、C++ API Reference、C++ Generated Code Guide和Encoding Reference。
我們將要使用的示例是一個非常簡單的“地址簿”應用程序,它 可以在文件中讀取和寫入人們的聯(lián)系方式。每個人在 地址簿具有姓名、ID、電子郵件地址和聯(lián)系電話 號碼。
如何序列化和檢索像這樣的結構化數(shù)據(jù)?有幾個 解決這個問題的方法:
- 原始存儲器中數(shù)據(jù)結構可以以二進制形式發(fā)送/保存。這是一種脆弱的方法,因為接收/閱讀代碼必須 使用完全相同的內存布局、字節(jié)序等編譯。此外,作為 文件以原始格式積累數(shù)據(jù),而軟件的副本 這種格式的有線傳播,很難擴展 格式。
- 您可以發(fā)明一種特別的方法來將數(shù)據(jù)項編碼為單個 字符串-例如將4個int編碼為“12:3:-23:67”。這是一個簡單的和 靈活的方法,雖然它確實需要編寫一次性編碼和 解析代碼,并且解析施加小的運行時成本。這樣最好 用于編碼非常簡單的數(shù)據(jù)。
- 將數(shù)據(jù)序列化為XML。這種方法非常有吸引力,因為XML是 (sort的)人類可讀,并且有許多 語言。如果你想與其他人共享數(shù)據(jù),這是一個很好的選擇 應用程序/項目。然而,XML是眾所周知的空間密集型,并且 編碼/解碼它可能對應用施加巨大的性能損失。 此外,導航XMLDOM樹比 在類中導航簡單字段通常是這樣的。
您可以使用Protobuf來代替這些選項。協(xié)議緩沖區(qū)是 靈活、高效、自動化的解決方案,以準確地解決這一問題。與 Protobuf,可以編寫數(shù)據(jù)結構的.proto描述 希望儲存。Protobuf編譯器由此創(chuàng)建一個類,該類 實現(xiàn)Protobuf數(shù)據(jù)的自動編碼和解析 高效二進制格式生成的類為 組成協(xié)議緩沖區(qū)的字段,并負責 作為一個單元閱讀協(xié)議緩沖器。重要的是,協(xié)議 緩沖區(qū)格式支持以這種方式隨時間擴展格式的想法 代碼仍然可以讀取用舊格式編碼的數(shù)據(jù)。
示例代碼
包含在源代碼包中的 “examples”目錄。
Defining Your Protocol Format
要創(chuàng)建地址簿應用程序,您需要從.proto開始 文件。.proto文件中的定義很簡單:您為以下對象添加消息 要序列化的每個數(shù)據(jù)結構,然后為其指定名稱和類型 消息中的每個字段。下面是定義消息的.proto文件, addressbook.proto.
syntax = "proto2";
package tutorial;
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}正如你所看到的,它的語法類似于C++或Java。讓我們把每一部分都看一遍 看看它做了什么。
.proto文件以包聲明開始,這有助于防止不同項目之間的命名沖突。在C++中,生成的類將放置在與包名稱匹配的命名空間中。
接下來,您有了消息定義。消息包含一組類型化字段。有許多標準的簡單數(shù)據(jù)類型可用作為字段類型,包括bool、int32、float、double和string。你呢 還可以通過使用其他消息類型為消息添加進一步的結構 字段類型-在上面的示例中,Person消息包含PhoneNumber AddressBook消息包含Person消息。你可以的 甚至定義嵌套在其他消息中的消息類型-正如您所看到的, PhoneNumber類型定義在Person內。您還可以定義enum類型 如果您希望您的一個字段具有預定義值列表中的一個, 在這里,您希望指定電話號碼可以是下列電話之一 類型:MOBILE、HOME或WORK。
每個元素上的“= 1”、“= 2”標記標識 字段在二進制編碼中使用。字段號1-15需要少一個字節(jié) 編碼比更高的數(shù)字,所以作為一個優(yōu)化,你可以決定使用這些 通常使用的或重復的元件的編號,留下字段編號16和18。 對于不太常用的可選元素,則更高。中的每個元素 字段需要重新編碼字段編號,因此重復的字段尤其 這是優(yōu)化的好方法。
每個字段必須使用以下修飾符之一進行注釋:
optional:字段可以設置,也可以不設置。如果是可選字段值 則使用默認值。對于簡單類型,可以指定 自己的默認值,就像我們在示例中對電話號碼type所做的那樣。 否則,將使用系統(tǒng)默認值:數(shù)值類型為零,為空 字符串為字符串,布爾為假。對于嵌入的消息,默認的 值始終是消息的“默認實例”或“原型”,它 沒有設置任何字段。調用訪問器以獲取 未始終顯式設置的可選(或必需)字段 返回該字段的默認值。repeated:字段可以重復任何次數(shù)(包括零次)。 重復值的順序將保留在協(xié)議緩沖區(qū)中。 將重復字段視為動態(tài)大小的數(shù)組。required:必須提供字段的值,否則消息 將被視為“未初始化”。如果libprotobuf在debug中編譯 模式下,序列化未初始化的消息將導致斷言失敗。 在優(yōu)化的生成中,將跳過檢查并寫入消息 不管怎樣。但是,解析未初始化的消息總是會失?。ㄍㄟ^ 從parse方法返回false)。除此之外,必填字段 行為與可選字段完全相同。
Important
Required Is Forever 您應該非常小心地將字段標記為 required. 如果在某個時候 如果您希望停止編寫或發(fā)送必填字段,則 將字段更改為可選字段-老讀者將考慮消息 而該字段是不完整的,并且可能無意中拒絕或丟棄它們。 您應該考慮為 你的緩沖器。在Google內部 required fields are strongly disfavored; most messages defined in proto2 syntax use 領域受到強烈反對; proto 2語法中定義的大多數(shù)消息使用 optional and 和 repeated only. (Proto3 does not support 只有 (Proto3不支持 required fields at all.) 所有的領域)。
您將找到編寫.proto文件的完整指南-包括所有 可能的字段類型-在協(xié)議緩沖區(qū)語言指南。 不要去尋找類似于類繼承的工具- protocol 緩沖器不會這樣做。
Compiling Your Protocol Buffers編譯協(xié)議緩沖區(qū)
現(xiàn)在您已經(jīng)有了.proto,接下來您需要做的就是生成 你需要讀和寫的類AddressBook(因此Person和 PhoneNumber)消息。為此,您需要運行協(xié)議緩沖區(qū) 編譯器protoc上的.proto:
1.如果你還沒有安裝編譯器, 下載軟件包并按照在README中的說明。
2.現(xiàn)在運行編譯器,指定源目錄(您的 應用程序的源代碼仍然存在-如果您 不提供值),目標目錄(您希望 生成代碼去;通常與$SRC_DIR相同),和路徑你的 .proto.在這種情況下,您……:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
因為需要C++類,所以使用--cpp_out選項-類似 為其他支持的語言提供了選項。
這將在指定的目標目錄中生成以下文件:
addressbook.pb.h, the header which declares your generated classes.addressbook.pb.cc, which contains the implementation of your classes.
The Protocol Buffer API協(xié)議緩沖區(qū)API
讓我們看一下生成的代碼,看看編譯器為您創(chuàng)建了哪些類和函數(shù)。如果查看addressbook.pb.h,可以看到在addressbook.proto中指定的每個消息都有一個類。仔細觀察Person類,可以看到編譯器為每個字段生成了訪問器。例如,對于name、id、email和phones字段,可以使用以下方法:
// name inline bool has_name() const; inline void clear_name(); inline const ::std::string& name() const; inline void set_name(const ::std::string& value); inline void set_name(const char* value); inline ::std::string* mutable_name(); // id inline bool has_id() const; inline void clear_id(); inline int32_t id() const; inline void set_id(int32_t value); // email inline bool has_email() const; inline void clear_email(); inline const ::std::string& email() const; inline void set_email(const ::std::string& value); inline void set_email(const char* value); inline ::std::string* mutable_email(); // phones inline int phones_size() const; inline void clear_phones(); inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phones() const; inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phones(); inline const ::tutorial::Person_PhoneNumber& phones(int index) const; inline ::tutorial::Person_PhoneNumber* mutable_phones(int index); inline ::tutorial::Person_PhoneNumber* add_phones();
正如您所看到的,getter的名稱與小寫的字段完全相同,而 setter方法以set_開始。也有has_方法 單數(shù)(必需或可選)字段,如果該字段已 準備好了。最后,每個字段都有一個clear_方法,用于將字段恢復為 空的狀態(tài)。
雖然數(shù)字id字段僅具有上述基本訪問器集, name和email字段有兩個額外的方法,因為它們 strings -一個mutable_ getter,讓你直接得到一個指向字符串的指針, 還有一只二傳手請注意,即使mutable_email()為 尚未設置;它將自動初始化為空字符串。如果你 如果在本例中有一個重復的消息字段,它也會有一個email 方法,而不是mutable_方法。
重復字段也有一些特殊的方法-如果您查看 重復的phones字段,您將看到
- 檢查重復字段的
_size(換句話說,有多少個電話號碼Person2#2)。 - 使用索引獲取指定的電話號碼。
- 更新指定索引處的現(xiàn)有電話號碼。
- 添加另一個電話號碼到消息,然后您可以編輯(重復 標量類型有一個
add_,它只是讓你傳入新值)。
有關協(xié)議編譯器為哪些成員生成的詳細信息 任何特定字段定義,請參見 C++生成代碼參考.
枚舉和嵌套類
生成的代碼包括一個與您的PhoneType對應的.proto枚舉 枚舉。您可以將此類型稱為Person::PhoneType,其值為 Person::MOBILE、Person::HOME和Person::WORK(實現(xiàn)細節(jié) 稍微復雜一點,但您不需要了解它們就可以使用 enum)。
編譯器還為您生成了一個名為 Person::PhoneNumber.如果你看一下代碼,你可以看到“真實的的” 類實際上被稱為Person_PhoneNumber,但內部定義了一個typedef Person允許您將其視為嵌套類。唯一的案子 這會產(chǎn)生不同的地方是,如果你想在 另一個文件-你不能在C++中正向聲明嵌套類型,但你可以 forward-declare Person_PhoneNumber。
標準消息方法
每個消息類還包含許多其他方法,這些方法允許您檢查或 操縱整個消息,包括:
bool IsInitialized() const;:檢查是否已輸入所有必填字段 準備好了。string DebugString() const;:返回一個人類可讀的 消息,對調試特別有用。void CopyFrom(const Person& from);:用給定的 消息的價值。void Clear();:將所有元素清零為空狀態(tài)。
這些方法和下一節(jié)中描述的I/O方法實現(xiàn)了 Message所有C++協(xié)議緩沖區(qū)類共享的接口。有關更多信息, 見 Message的完整API文檔。
解析和序列化
最后,每個協(xié)議緩沖區(qū)類都有寫入和閱讀消息的方法 所選類型的二進制格式這些措施 包括:
bool SerializeToString(string* output) const;:序列化消息并 存儲給定字符串中的字節(jié)。注意,字節(jié)是二進制的,而不是 text;我們只使用string類作為方便的容器。bool ParseFromString(const string& data);:從給定的 字符串bool SerializeToOstream(ostream* output) const;:將消息寫入 C++ #2bool ParseFromIstream(istream* input);:從給定的 C++istream。
這些只是為解析和序列化提供的幾個選項。 再一次,請參閱 Message API參考 一份完整的名單。
寫消息
現(xiàn)在讓我們嘗試使用協(xié)議緩沖區(qū)類。你首先要做的 地址簿應用程序能做的就是把個人詳細信息寫到你的 地址簿文件。為此,您需要創(chuàng)建并填充 協(xié)議緩沖器類,然后將它們寫入輸出流。
下面是一個程序,它從文件中讀取AddressBook,添加一個新的 Person基于用戶輸入將新的AddressBook寫回到它,并將新的undefined寫回 文件再次。直接調用或引用由 協(xié)議編譯器突出顯示。
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, '\n');
cout << "Enter name: ";
getline(cin, *person->mutable_name());
cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
}
while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
// Add an address.
PromptForAddress(address_book.add_people());
{
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}注意GOOGLE_PROTOBUF_VERIFY_VERSION宏。這是一個很好的實踐-雖然 不是絕對必要的-在使用C++協(xié)議之前執(zhí)行此宏 緩沖庫。它驗證您沒有意外地鏈接到 版本的庫,這是不兼容的版本的標題你 編譯。如果檢測到版本不匹配,程序將中止。注意事項 每個.pb.cc文件在啟動時自動調用此宏。
還請注意在程序結束時對ShutdownProtobufLibrary()的調用。 所有這一切都是刪除任何全局對象分配的協(xié)議 緩沖庫。這對于大多數(shù)程序來說是不必要的,因為這個過程是 無論如何都要退出,操作系統(tǒng)將負責回收其所有內存。 但是,如果使用的內存泄漏檢查器要求 或者如果你正在寫一個可以被加載和卸載的庫 多次,則您可能需要強制Protocol Buffers 清理一切
定義消息類型
首先讓我們看一個非常簡單的例子。假設您要定義一個 請求消息格式,其中每個搜索請求都有一個查詢字符串, 您感興趣的結果的特定頁面,以及每個頁面的結果數(shù)量。下面是您用來定義消息類型的.proto文件。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}文件的第一行指定您使用的是proto3語法:如果 如果您不這樣做,協(xié)議緩沖區(qū)編譯器將假定您使用的是原型2.這一定是 文件的第一個非空、非注釋行。SearchRequest消息定義指定了三個字段(名稱/值 對),一個用于要包含在此類型 留言每個字段都有一個名稱和類型。 指定字段類型
在前面的示例中,所有字段都是標量類型:兩個整數(shù) (page_number和results_per_page)和字符串(query)。也可以指定枚舉和復合類型,如其他消息類型的領域。
到此這篇關于C++ Protobuf的學習使用指南的文章就介紹到這了,更多相關C++ Protobuf內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++數(shù)據(jù)精度問題的解決方案(對浮點數(shù)保存指定位小數(shù))
對浮點數(shù)保存指定位小數(shù),怎么解決這個問題呢?如果有小伙伴對C++數(shù)據(jù)精度問題的解決方案感興趣的朋友一起看看吧2017-08-08

