欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++?Protobuf的學(xué)習(xí)使用指南

 更新時(shí)間:2023年07月19日 14:08:51   作者:藍(lán)天巨人  
protocol?buffers是一種語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)的方法,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲(chǔ)等,下面就來(lái)跟隨小編一起簡(jiǎn)單學(xué)習(xí)一下它的使用吧

簡(jiǎn)介

protocol buffers 是一種語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)的方法,它可用于(數(shù)據(jù))通信協(xié)議數(shù)據(jù)存儲(chǔ)等。通信時(shí)所傳遞的信息是通過(guò)Protobuf定義的message數(shù)據(jù)結(jié)構(gòu)進(jìn)行打包,然后編譯成二進(jìn)制的碼流再進(jìn)行傳輸或者存儲(chǔ)。

Protocol buffers 是一種靈活,高效,自動(dòng)化機(jī)制的結(jié)構(gòu)數(shù)據(jù)序列化方法-可類(lèi)比 XML,但是比 XML 更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單。

Protobuf 使用的時(shí)候必須寫(xiě)一個(gè) IDL(Interface description language)文件,在里面定義好數(shù)據(jù)結(jié)構(gòu),只有預(yù)先定義了的數(shù)據(jù)結(jié)構(gòu),才能被序列化和反序列化。其中,序列化是將對(duì)象轉(zhuǎn)換二進(jìn)制數(shù)據(jù),反序列化是將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成對(duì)象。

教程

本教程提供了一個(gè)基本的C++程序員介紹使用協(xié)議緩沖區(qū)。通過(guò)創(chuàng)建一個(gè)簡(jiǎn)單的示例應(yīng)用程序,它向您展示了如何

  • 在.proto文件中定義消息格式。
  • 使用協(xié)議緩沖區(qū)編譯器。
  • 使用C++協(xié)議緩沖區(qū)API寫(xiě)入和讀取消息。

這不是一個(gè)在C++中使用協(xié)議緩沖區(qū)的全面指南。有關(guān)更多詳細(xì)的參考信息,請(qǐng)參見(jiàn)Protocol Buffer Language Guide(proto2)、Protocol Buffer Language Guide(proto3)、C++ API Reference、C++ Generated Code Guide和Encoding Reference。

我們將要使用的示例是一個(gè)非常簡(jiǎn)單的“地址簿”應(yīng)用程序,它 可以在文件中讀取和寫(xiě)入人們的聯(lián)系方式。每個(gè)人在 地址簿具有姓名、ID、電子郵件地址和聯(lián)系電話(huà) 號(hào)碼。

如何序列化和檢索像這樣的結(jié)構(gòu)化數(shù)據(jù)?有幾個(gè) 解決這個(gè)問(wèn)題的方法:

  • 原始存儲(chǔ)器中數(shù)據(jù)結(jié)構(gòu)可以以二進(jìn)制形式發(fā)送/保存。這是一種脆弱的方法,因?yàn)榻邮?閱讀代碼必須 使用完全相同的內(nèi)存布局、字節(jié)序等編譯。此外,作為 文件以原始格式積累數(shù)據(jù),而軟件的副本 這種格式的有線(xiàn)傳播,很難擴(kuò)展 格式。
  • 您可以發(fā)明一種特別的方法來(lái)將數(shù)據(jù)項(xiàng)編碼為單個(gè) 字符串-例如將4個(gè)int編碼為“12:3:-23:67”。這是一個(gè)簡(jiǎn)單的和 靈活的方法,雖然它確實(shí)需要編寫(xiě)一次性編碼和 解析代碼,并且解析施加小的運(yùn)行時(shí)成本。這樣最好 用于編碼非常簡(jiǎn)單的數(shù)據(jù)。
  • 將數(shù)據(jù)序列化為XML。這種方法非常有吸引力,因?yàn)閄ML是 (sort的)人類(lèi)可讀,并且有許多 語(yǔ)言。如果你想與其他人共享數(shù)據(jù),這是一個(gè)很好的選擇 應(yīng)用程序/項(xiàng)目。然而,XML是眾所周知的空間密集型,并且 編碼/解碼它可能對(duì)應(yīng)用施加巨大的性能損失。 此外,導(dǎo)航XMLDOM樹(shù)比 在類(lèi)中導(dǎo)航簡(jiǎn)單字段通常是這樣的。

您可以使用Protobuf來(lái)代替這些選項(xiàng)。協(xié)議緩沖區(qū)是 靈活、高效、自動(dòng)化的解決方案,以準(zhǔn)確地解決這一問(wèn)題。與 Protobuf,可以編寫(xiě)數(shù)據(jù)結(jié)構(gòu)的.proto描述 希望儲(chǔ)存。Protobuf編譯器由此創(chuàng)建一個(gè)類(lèi),該類(lèi) 實(shí)現(xiàn)Protobuf數(shù)據(jù)的自動(dòng)編碼和解析 高效二進(jìn)制格式生成的類(lèi)為 組成協(xié)議緩沖區(qū)的字段,并負(fù)責(zé) 作為一個(gè)單元閱讀協(xié)議緩沖器。重要的是,協(xié)議 緩沖區(qū)格式支持以這種方式隨時(shí)間擴(kuò)展格式的想法 代碼仍然可以讀取用舊格式編碼的數(shù)據(jù)。

示例代碼

包含在源代碼包中的 “examples”目錄

Defining Your Protocol Format

要?jiǎng)?chuàng)建地址簿應(yīng)用程序,您需要從.proto開(kāi)始 文件。.proto文件中的定義很簡(jiǎn)單:您為以下對(duì)象添加消息 要序列化的每個(gè)數(shù)據(jù)結(jié)構(gòu),然后為其指定名稱(chēng)和類(lèi)型 消息中的每個(gè)字段。下面是定義消息的.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;
}

正如你所看到的,它的語(yǔ)法類(lèi)似于C++或Java。讓我們把每一部分都看一遍 看看它做了什么。

.proto文件以包聲明開(kāi)始,這有助于防止不同項(xiàng)目之間的命名沖突。在C++中,生成的類(lèi)將放置在與包名稱(chēng)匹配的命名空間中。

接下來(lái),您有了消息定義。消息包含一組類(lèi)型化字段。有許多標(biāo)準(zhǔn)的簡(jiǎn)單數(shù)據(jù)類(lèi)型可用作為字段類(lèi)型,包括bool、int32、floatdoublestring。你呢 還可以通過(guò)使用其他消息類(lèi)型為消息添加進(jìn)一步的結(jié)構(gòu) 字段類(lèi)型-在上面的示例中,Person消息包含PhoneNumber AddressBook消息包含Person消息。你可以的 甚至定義嵌套在其他消息中的消息類(lèi)型-正如您所看到的, PhoneNumber類(lèi)型定義在Person內(nèi)。您還可以定義enum類(lèi)型 如果您希望您的一個(gè)字段具有預(yù)定義值列表中的一個(gè), 在這里,您希望指定電話(huà)號(hào)碼可以是下列電話(huà)之一 類(lèi)型:MOBILEHOMEWORK。

每個(gè)元素上的“= 1”、“= 2”標(biāo)記標(biāo)識(shí) 字段在二進(jìn)制編碼中使用。字段號(hào)1-15需要少一個(gè)字節(jié) 編碼比更高的數(shù)字,所以作為一個(gè)優(yōu)化,你可以決定使用這些 通常使用的或重復(fù)的元件的編號(hào),留下字段編號(hào)16和18。 對(duì)于不太常用的可選元素,則更高。中的每個(gè)元素 字段需要重新編碼字段編號(hào),因此重復(fù)的字段尤其 這是優(yōu)化的好方法。

每個(gè)字段必須使用以下修飾符之一進(jìn)行注釋?zhuān)?/p>

  • optional:字段可以設(shè)置,也可以不設(shè)置。如果是可選字段值 則使用默認(rèn)值。對(duì)于簡(jiǎn)單類(lèi)型,可以指定 自己的默認(rèn)值,就像我們?cè)谑纠袑?duì)電話(huà)號(hào)碼type所做的那樣。 否則,將使用系統(tǒng)默認(rèn)值:數(shù)值類(lèi)型為零,為空 字符串為字符串,布爾為假。對(duì)于嵌入的消息,默認(rèn)的 值始終是消息的“默認(rèn)實(shí)例”或“原型”,它 沒(méi)有設(shè)置任何字段。調(diào)用訪(fǎng)問(wèn)器以獲取 未始終顯式設(shè)置的可選(或必需)字段 返回該字段的默認(rèn)值。
  • repeated:字段可以重復(fù)任何次數(shù)(包括零次)。 重復(fù)值的順序?qū)⒈A粼趨f(xié)議緩沖區(qū)中。 將重復(fù)字段視為動(dòng)態(tài)大小的數(shù)組。
  • required:必須提供字段的值,否則消息 將被視為“未初始化”。如果libprotobuf在debug中編譯 模式下,序列化未初始化的消息將導(dǎo)致斷言失敗。 在優(yōu)化的生成中,將跳過(guò)檢查并寫(xiě)入消息 不管怎樣。但是,解析未初始化的消息總是會(huì)失敗(通過(guò) 從parse方法返回false)。除此之外,必填字段 行為與可選字段完全相同。

Important

Required Is Forever 您應(yīng)該非常小心地將字段標(biāo)記為 required. 如果在某個(gè)時(shí)候 如果您希望停止編寫(xiě)或發(fā)送必填字段,則 將字段更改為可選字段-老讀者將考慮消息 而該字段是不完整的,并且可能無(wú)意中拒絕或丟棄它們。 您應(yīng)該考慮為 你的緩沖器。在Google內(nèi)部 required fields are strongly disfavored; most messages defined in proto2 syntax use 領(lǐng)域受到強(qiáng)烈反對(duì); proto 2語(yǔ)法中定義的大多數(shù)消息使用 optional and 和 repeated only. (Proto3 does not support 只有 (Proto3不支持 required fields at all.) 所有的領(lǐng)域)。

您將找到編寫(xiě).proto文件的完整指南-包括所有 可能的字段類(lèi)型-在協(xié)議緩沖區(qū)語(yǔ)言指南。 不要去尋找類(lèi)似于類(lèi)繼承的工具- protocol 緩沖器不會(huì)這樣做。

Compiling Your Protocol Buffers編譯協(xié)議緩沖區(qū)

現(xiàn)在您已經(jīng)有了.proto,接下來(lái)您需要做的就是生成 你需要讀和寫(xiě)的類(lèi)AddressBook(因此Person和 PhoneNumber)消息。為此,您需要運(yùn)行協(xié)議緩沖區(qū) 編譯器protoc上的.proto

1.如果你還沒(méi)有安裝編譯器, 下載軟件包并按照在README中的說(shuō)明。

2.現(xiàn)在運(yùn)行編譯器,指定源目錄(您的 應(yīng)用程序的源代碼仍然存在-如果您 不提供值),目標(biāo)目錄(您希望 生成代碼去;通常與$SRC_DIR相同),和路徑你的 .proto.在這種情況下,您……:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

因?yàn)樾枰狢++類(lèi),所以使用--cpp_out選項(xiàng)-類(lèi)似 為其他支持的語(yǔ)言提供了選項(xiàng)。

這將在指定的目標(biāo)目錄中生成以下文件:

  • 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)建了哪些類(lèi)和函數(shù)。如果查看addressbook.pb.h,可以看到在addressbook.proto中指定的每個(gè)消息都有一個(gè)類(lèi)。仔細(xì)觀察Person類(lèi),可以看到編譯器為每個(gè)字段生成了訪(fǎng)問(wèn)器。例如,對(duì)于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的名稱(chēng)與小寫(xiě)的字段完全相同,而 setter方法以set_開(kāi)始。也有has_方法 單數(shù)(必需或可選)字段,如果該字段已 準(zhǔn)備好了。最后,每個(gè)字段都有一個(gè)clear_方法,用于將字段恢復(fù)為 空的狀態(tài)。

雖然數(shù)字id字段僅具有上述基本訪(fǎng)問(wèn)器集, nameemail字段有兩個(gè)額外的方法,因?yàn)樗鼈?strings -一個(gè)mutable_ getter,讓你直接得到一個(gè)指向字符串的指針, 還有一只二傳手請(qǐng)注意,即使mutable_email()為 尚未設(shè)置;它將自動(dòng)初始化為空字符串。如果你 如果在本例中有一個(gè)重復(fù)的消息字段,它也會(huì)有一個(gè)email 方法,而不是mutable_方法。

重復(fù)字段也有一些特殊的方法-如果您查看 重復(fù)的phones字段,您將看到

  • 檢查重復(fù)字段的_size(換句話(huà)說(shuō),有多少個(gè)電話(huà)號(hào)碼 Person2#2)。
  • 使用索引獲取指定的電話(huà)號(hào)碼。
  • 更新指定索引處的現(xiàn)有電話(huà)號(hào)碼。
  • 添加另一個(gè)電話(huà)號(hào)碼到消息,然后您可以編輯(重復(fù) 標(biāo)量類(lèi)型有一個(gè)add_,它只是讓你傳入新值)。

有關(guān)協(xié)議編譯器為哪些成員生成的詳細(xì)信息 任何特定字段定義,請(qǐng)參見(jiàn) C++生成代碼參考.

枚舉和嵌套類(lèi)

生成的代碼包括一個(gè)與您的PhoneType對(duì)應(yīng)的.proto枚舉 枚舉。您可以將此類(lèi)型稱(chēng)為Person::PhoneType,其值為 Person::MOBILE、Person::HOMEPerson::WORK(實(shí)現(xiàn)細(xì)節(jié) 稍微復(fù)雜一點(diǎn),但您不需要了解它們就可以使用 enum)。

編譯器還為您生成了一個(gè)名為 Person::PhoneNumber.如果你看一下代碼,你可以看到“真實(shí)的的” 類(lèi)實(shí)際上被稱(chēng)為Person_PhoneNumber,但內(nèi)部定義了一個(gè)typedef Person允許您將其視為嵌套類(lèi)。唯一的案子 這會(huì)產(chǎn)生不同的地方是,如果你想在 另一個(gè)文件-你不能在C++中正向聲明嵌套類(lèi)型,但你可以 forward-declare Person_PhoneNumber

標(biāo)準(zhǔn)消息方法

每個(gè)消息類(lèi)還包含許多其他方法,這些方法允許您檢查或 操縱整個(gè)消息,包括:

  • bool IsInitialized() const;:檢查是否已輸入所有必填字段 準(zhǔn)備好了。
  • string DebugString() const;:返回一個(gè)人類(lèi)可讀的 消息,對(duì)調(diào)試特別有用。
  • void CopyFrom(const Person& from);:用給定的 消息的價(jià)值。
  • void Clear();:將所有元素清零為空狀態(tài)。

這些方法和下一節(jié)中描述的I/O方法實(shí)現(xiàn)了 Message所有C++協(xié)議緩沖區(qū)類(lèi)共享的接口。有關(guān)更多信息, 見(jiàn) Message的完整API文檔。

解析和序列化

最后,每個(gè)協(xié)議緩沖區(qū)類(lèi)都有寫(xiě)入和閱讀消息的方法 所選類(lèi)型的二進(jìn)制格式這些措施 包括:

  • bool SerializeToString(string* output) const;:序列化消息并 存儲(chǔ)給定字符串中的字節(jié)。注意,字節(jié)是二進(jìn)制的,而不是 text;我們只使用string類(lèi)作為方便的容器。
  • bool ParseFromString(const string& data);:從給定的 字符串
  • bool SerializeToOstream(ostream* output) const;:將消息寫(xiě)入 C++ #2
  • bool ParseFromIstream(istream* input);:從給定的 C++ istream。

這些只是為解析和序列化提供的幾個(gè)選項(xiàng)。 再一次,請(qǐng)參閱 Message API參考 一份完整的名單。

寫(xiě)消息

現(xiàn)在讓我們嘗試使用協(xié)議緩沖區(qū)類(lèi)。你首先要做的 地址簿應(yīng)用程序能做的就是把個(gè)人詳細(xì)信息寫(xiě)到你的 地址簿文件。為此,您需要?jiǎng)?chuàng)建并填充 協(xié)議緩沖器類(lèi),然后將它們寫(xiě)入輸出流。

下面是一個(gè)程序,它從文件中讀取AddressBook,添加一個(gè)新的 Person基于用戶(hù)輸入將新的AddressBook寫(xiě)回到它,并將新的undefined寫(xiě)回 文件再次。直接調(diào)用或引用由 協(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宏。這是一個(gè)很好的實(shí)踐-雖然 不是絕對(duì)必要的-在使用C++協(xié)議之前執(zhí)行此宏 緩沖庫(kù)。它驗(yàn)證您沒(méi)有意外地鏈接到 版本的庫(kù),這是不兼容的版本的標(biāo)題你 編譯。如果檢測(cè)到版本不匹配,程序?qū)⒅兄埂W⒁馐马?xiàng) 每個(gè).pb.cc文件在啟動(dòng)時(shí)自動(dòng)調(diào)用此宏。

還請(qǐng)注意在程序結(jié)束時(shí)對(duì)ShutdownProtobufLibrary()的調(diào)用。 所有這一切都是刪除任何全局對(duì)象分配的協(xié)議 緩沖庫(kù)。這對(duì)于大多數(shù)程序來(lái)說(shuō)是不必要的,因?yàn)檫@個(gè)過(guò)程是 無(wú)論如何都要退出,操作系統(tǒng)將負(fù)責(zé)回收其所有內(nèi)存。 但是,如果使用的內(nèi)存泄漏檢查器要求 或者如果你正在寫(xiě)一個(gè)可以被加載和卸載的庫(kù) 多次,則您可能需要強(qiáng)制Protocol Buffers 清理一切

定義消息類(lèi)型

首先讓我們看一個(gè)非常簡(jiǎn)單的例子。假設(shè)您要定義一個(gè) 請(qǐng)求消息格式,其中每個(gè)搜索請(qǐng)求都有一個(gè)查詢(xún)字符串, 您感興趣的結(jié)果的特定頁(yè)面,以及每個(gè)頁(yè)面的結(jié)果數(shù)量。下面是您用來(lái)定義消息類(lèi)型的.proto文件。

syntax = "proto3";
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 results_per_page = 3;
}

文件的第一行指定您使用的是proto3語(yǔ)法:如果 如果您不這樣做,協(xié)議緩沖區(qū)編譯器將假定您使用的是原型2.這一定是 文件的第一個(gè)非空、非注釋行。SearchRequest息定義指定了三個(gè)字段(名稱(chēng)/值 對(duì)),一個(gè)用于要包含在此類(lèi)型 留言每個(gè)字段都有一個(gè)名稱(chēng)和類(lèi)型。 指定字段類(lèi)型

在前面的示例中,所有字段都是標(biāo)量類(lèi)型:兩個(gè)整數(shù) (page_numberresults_per_page)和字符串(query)。也可以指定枚舉和復(fù)合類(lèi)型,如其他消息類(lèi)型的領(lǐng)域。

到此這篇關(guān)于C++ Protobuf的學(xué)習(xí)使用指南的文章就介紹到這了,更多相關(guān)C++ Protobuf內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論