Google Protocol Buffer (PB)简明入门

转载 2016年08月30日 16:31:31
写在最前:本文以在Windows下C++中使用Protocol Buffer(以下简称PB)为例展示如何安装和使用PB。其他语言和平台也可做为参考。

一.什么是PB

PB是Google开发的一种开源数据交换方式。特别适合于在RPC间交换对象及数据结构。与其相似的应用有XML、JSON、THRIFT等。

二.为什么要用PB

相对于其主要同类应用XML,PB的主要优势在于更小的数据size(比XML小3-10倍)和更快的解析速度(比XML快20-100倍),同时在使用上也更简单。PB的劣势主要是可读性比较差,由于其生成的是二进制数据,可读性要远低于XML的明文格式,同时编辑也要借助代码来完成(XML可以直接编辑)。

三.安装

1.      下载PB

https://code.google.com/p/protobuf/downloads/list下载最新版PB。建议最好是下载源代码(也提供了Binary下载),然后自己来编译。以下主要以windows下的编译做示例,Linux下的可以参考自行完成。(Windows下的下载包protobuf-2.5.0.zip

2.      编译

在vsprojects文件夹下可以找到解决方案protobuf.sln(该方案是用VS2008生成的,低版本的VS可以使用目录下提供的Linux脚本convert2008to2005.sh降级),用VS打开后直接启动编译即可。编译完成后会生成一个主要的exe和三个.lib库,分别是protoc.exe, libprotobuf.lib, libprotobuf-lite.lib, libprotoc.lib. 其中protoc.exe是用于编译.proto文件的,其他三个库是用于编译序列化/反序列化代码时使用的。(稍后会讲到)

四.使用

1.      构建对象描述文件(.proto文件)

要使用PB,首先你要构建一个对象描述文件。见下例:

person.proto

[cpp] view plain copy
  1. // 包名。编译后就是名空间名称  
  2. packagemrPerson;  
  3.    
  4. // 对象名。在该例中,对象名称为Person  
  5. messagePerson  
  6. {  
  7. // 字段的属性、类型、名称、ID  
  8.          optionalstring name = 1;  
  9.          optionalint32 age = 2;   
  10. }  
上面是一个简单的对象描述文件。有点类似于伪代码。这里解释一下字段的属性、类型、名称及ID。字段的属性分为三种: required, optional, repeated. 分别表示该字段是必须的,可选的及重复的。具体含义如下:

Each field mustbe annotated with one of the following modifiers:

·        required: a value forthe field must be provided, otherwise the message will be considered"uninitialized". If libprotobuf is compiled in debug mode, serializing an uninitialized message will causean assertion failure. In optimized builds, the check is skipped and the messagewill be written anyway. However, parsing an uninitialized message will alwaysfail (by returning false from the parse method). Other than this, a required field behaves exactlylike an optional field.

·        optional: the fieldmay or may not be set. If an optional field value isn't set, a default value isused. For simple types, you can specify your own default value, as we've donefor the phone number type in the example. Otherwise, a system default is used: zero for numerictypes, the empty string for strings, false for bools. For embedded messages,the default value is always the "default instance" or"prototype" of the message, which has none of its fields set. Callingthe accessor to get the value of an optional (or required) field which has notbeen explicitly set always returns that field's default value.

·        repeated: the fieldmay be repeated any number of times (including zero). The order of the repeatedvalues will be preserved in the protocol buffer. Think of repeated fields asdynamically sized arrays.

概括一下,required属性的字段是必须要给初值的,否则解析时会返回false;optional如果没给初值,PB会使用默认初值;repeated代表的是数组。注意:Google PB官方文档中特意指明,从Google内部目前使用情况来看,一个比较好的实践方式是只使用optionalrepeated,而不使用required。这样可以达到最好的向前兼容性。

字段类型列表如下:(主要用到的是double,int64,int32,string,byte等)

.proto Type

Notes

C++ Type

Java Type

PythonType[2]

double

double

double

float

float

float

float

float

int32

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.

int32

int

int

int64

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.

int64

long

int/long[3]

uint32

Uses variable-length encoding.

uint32

int[1]

int/long[3]

uint64

Uses variable-length encoding.

uint64

long[1]

int/long[3]

sint32

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.

int32

int

int

sint64

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.

int64

long

int/long[3]

fixed32

Always four bytes. More efficient than uint32 if values are often greater than 228.

uint32

int[1]

int

fixed64

Always eight bytes. More efficient than uint64 if values are often greater than 256.

uint64

long[1]

int/long[3]

sfixed32

Always four bytes.

int32

int

int

sfixed64

Always eight bytes.

int64

long

int/long[3]

bool

bool

boolean

boolean

string

A string must always contain UTF-8 encoded or 7-bit ASCII text.

string

String

str/unicode[4]

bytes

May contain any arbitrary sequence of bytes.

string

ByteString

str

字段ID的主要作用是做向前兼容。.proto文件扩充后,可以通过定义不重复的id,实现向前兼容性。

2.      将对象描述文件编译成代码

使用如下命令行“protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/person.proto”来编译.proto文件。注意:$SRC_DIR表示.proto文件夹路径;$DST_DIR表示输出的.cc和.h文件夹路径;”.”号表示使用当前路径。编译完成后,会在$DST_DIR下生成两个文件“person.pb.h”和“person.pb.cc”。

3.      使用PB序列化和反序列化对象

代码说话:

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <fstream>  
  3. #include <string>  
  4. #include "person.pb.h"  
  5. using namespace std;  
  6. using namespace mrPerson;  
  7.    
  8. #pragma comment(lib,"libprotobuf.lib")  
  9. #pragma comment(lib,"libprotoc.lib")  
  10. #pragma comment(lib,"libprotobuf-lite.lib")  
  11.    
  12. // Main function:  Reads the entire address book from a file,  
  13. //  adds one person based on user input, then writes it back out to the same  
  14. //  file.  
  15. int main(int argc, char* argv[]) {  
  16.  // Verify that the version of the library that we linked against is  
  17.  // compatible with the version of the headers we compiled against.  
  18.  GOOGLE_PROTOBUF_VERIFY_VERSION;  
  19.    
  20.  // 定义Person对象  
  21.  Person person;  
  22.  // 设置字段  
  23.  person.set_name("huangzhidan");  
  24.  person.set_age(17);  
  25.    
  26.  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  
  27.  // 以下展示使用文件做为载体序列化和反序列化对象  
  28.  // 将对象写入文件  
  29.  fstream output("test_person.dat", ios::out | ios::trunc | ios::binary);  
  30.  if(!person.SerializeToOstream(&output))  
  31.  {  
  32.    cerr << "Failed to write person." << endl;  
  33.    return -1;  
  34.  }  
  35.  output.flush();  
  36.    
  37.  // 将文件反序列化成对象  
  38.  Person person_out;  
  39.  fstream input("test_person.dat", ios::in | ios::binary);  
  40.  if(!person_out.ParseFromIstream(&input))  
  41.  {  
  42.    cerr << "Failed to read person." << endl;  
  43.    return -1;  
  44.  }  
  45.    
  46.  // 验证  
  47.  string name = person_out.name();  
  48.  int age = person_out.age();  
  49.    
  50.  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  
  51.  // 以下展示使用流数据做为载体序列化和反序列化对象  
  52.  string streamData;  
  53.  // 可以使用SerializeToString接口将对象序列化成流数据,然后通过Socket通信  
  54.  person.SerializeToString(&streamData);  
  55.    
  56.  Person person_stream;  
  57.  person_stream.ParseFromString(streamData);  
  58.    
  59.  // 验证  
  60.  name = person_stream.name();  
  61.  age = person_stream.age();  
  62.    
  63.  // Optional:  Delete all globalobjects allocated by libprotobuf.  
  64.  google::protobuf::ShutdownProtobufLibrary();  
  65.    
  66.  return 0;  
  67. }  


以上代码演示了在PB中使用文件和流数据为载体来序列化和反序列化对象(库和头文件路径请自行配置)。可以看到,使用PB承载对象还是比较容易的,代码编写也很简单。

下面是使用PB来承载一个较复杂的嵌套对象过程:

(1)    addressbook.proto

[cpp] view plain copy
  1. //See README.txt for information and build instructions.  
  2.    
  3. packagetutorial;  
  4.    
  5. optionjava_package = "com.example.tutorial";  
  6. optionjava_outer_classname = "AddressBookProtos";  
  7.    
  8. messagePerson {  
  9.   required string name = 1;  
  10.   required int32 id = 2;        // Unique ID number for this person.  
  11.   optional string email = 3;  
  12.    
  13.   enum PhoneType {  
  14.     MOBILE = 0;  
  15.     HOME = 1;  
  16.     WORK = 2;  
  17.   }  
  18.    
  19.   message PhoneNumber {  
  20.     required string number = 1;  
  21.     optional PhoneType type = 2 [default =HOME];  
  22.   }  
  23.    
  24.   repeated PhoneNumber phone = 4;  
  25. }  
  26.    
  27. //Our address book file is just one of these.  
  28. messageAddressBook {  
  29.   repeated Person person = 1;  
  30. }  

(2)    参考以上编译成addressbook.pb.h和addressbook.pb.cc

(3)    Writing A Message

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <fstream>  
  3. #include <string>  
  4. #include "addressbook.pb.h"  
  5. using namespace std;  
  6.    
  7. // This function fills in a Personmessage based on user input.  
  8. voidPromptForAddress(tutorial::Person* person) {  
  9.  cout << "Enter person ID number: ";  
  10.  int id;  
  11.  cin >> id;  
  12.  person->set_id(id);  
  13.  cin.ignore(256, '\n');  
  14.    
  15.  cout << "Enter name: ";  
  16.  getline(cin, *person->mutable_name());  
  17.    
  18.  cout << "Enter email address (blank for none): ";  
  19.  string email;  
  20.  getline(cin, email);  
  21.  if (!email.empty()) {  
  22.    person->set_email(email);  
  23.  }  
  24.    
  25.  while (true) {  
  26.    cout << "Enter a phone number (or leave blank to finish):";  
  27.    string number;  
  28.    getline(cin, number);  
  29.    if (number.empty()) {  
  30.      break;  
  31.    }  
  32.    
  33.    tutorial::Person::PhoneNumber* phone_number = person->add_phone();  
  34.    phone_number->set_number(number);  
  35.    
  36.    cout << "Is this a mobile, home, or work phone? ";  
  37.    string type;  
  38.    getline(cin, type);  
  39.    if (type == "mobile") {  
  40.      phone_number->set_type(tutorial::Person::MOBILE);  
  41.    } else if (type == "home") {  
  42.      phone_number->set_type(tutorial::Person::HOME);  
  43.    } else if (type == "work") {  
  44.      phone_number->set_type(tutorial::Person::WORK);  
  45.    } else {  
  46.      cout << "Unknown phone type. Using default." << endl;  
  47.    }  
  48.  }  
  49. }  
  50.    
  51. // Main function:  Reads the entire address book from a file,  
  52. //  adds one person based on user input, then writes it back out to the same  
  53. //  file.  
  54. int main(int argc, char* argv[]) {  
  55.  // Verify that the version of the library that we linked against is  
  56.  // compatible with the version of the headers we compiled against.  
  57.  GOOGLE_PROTOBUF_VERIFY_VERSION;  
  58.    
  59.  if (argc != 2) {  
  60.    cerr << "Usage:  "<< argv[0] << " ADDRESS_BOOK_FILE" << endl;  
  61.    return -1;  
  62.  }  
  63.    
  64.  tutorial::AddressBook address_book;  
  65.    
  66.  {  
  67.    // Read the existing address book.  
  68.    fstream input(argv[1], ios::in | ios::binary);  
  69.    if (!input) {  
  70.      cout << argv[1] << ": File not found.  Creating a new file." << endl;  
  71.    } else if (!address_book.ParseFromIstream(&input)) {  
  72.      cerr << "Failed to parse address book." << endl;  
  73.      return -1;  
  74.    }  
  75.  }  
  76.    
  77.  // Add an address.  
  78.  PromptForAddress(address_book.add_person());  
  79.    
  80.  {  
  81.    // Write the new address book back to disk.  
  82.    fstream output(argv[1], ios::out | ios::trunc | ios::binary);  
  83.    if (!address_book.SerializeToOstream(&output)) {  
  84.      cerr << "Failed to write address book." << endl;  
  85.      return -1;  
  86.    }  
  87.  }  
  88.    
  89.  // Optional:  Delete all globalobjects allocated by libprotobuf.  
  90.  google::protobuf::ShutdownProtobufLibrary();  
  91.    
  92.  return 0;  
  93. }  

(4)    Reading A Message

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <fstream>  
  3. #include <string>  
  4. #include "addressbook.pb.h"  
  5. using namespace std;  
  6.    
  7. // Iterates though all people in theAddressBook and prints info about them.  
  8. void ListPeople(consttutorial::AddressBook& address_book) {  
  9.  for (int i = 0; i < address_book.person_size(); i++) {  
  10.    const tutorial::Person& person = address_book.person(i);  
  11.    
  12.    cout << "Person ID: " << person.id() <<endl;  
  13.    cout << "  Name: "<< person.name() << endl;  
  14.    if (person.has_email()) {  
  15.      cout << "  E-mailaddress: " << person.email() << endl;  
  16.    }  
  17.    
  18.    for (int j = 0; j < person.phone_size(); j++) {  
  19.      const tutorial::Person::PhoneNumber& phone_number = person.phone(j);  
  20.    
  21.      switch (phone_number.type()) {  
  22.        case tutorial::Person::MOBILE:  
  23.           cout << "  Mobile phone #: ";  
  24.           break;  
  25.        case tutorial::Person::HOME:  
  26.           cout << "  Home phone #: ";  
  27.           break;  
  28.        case tutorial::Person::WORK:  
  29.           cout << "  Work phone #: ";  
  30.           break;  
  31.      }  
  32.      cout << phone_number.number() << endl;  
  33.    }  
  34.  }  
  35. }  
  36.    
  37. // Main function:  Reads the entire address book from a file andprints all  
  38. //  the information inside.  
  39. int main(int argc, char* argv[]) {  
  40.  // Verify that the version of the library that we linked against is  
  41.  // compatible with the version of the headers we compiled against.  
  42.  GOOGLE_PROTOBUF_VERIFY_VERSION;  
  43.    
  44.  if (argc != 2) {  
  45.    cerr << "Usage:  "<< argv[0] << " ADDRESS_BOOK_FILE" << endl;  
  46.    return -1;  
  47.  }  
  48.    
  49.  tutorial::AddressBook address_book;  
  50.    
  51.  {  
  52.    // Read the existing address book.  
  53.    fstream input(argv[1], ios::in | ios::binary);  
  54.    if (!address_book.ParseFromIstream(&input)) {  
  55.      cerr << "Failed to parse address book." << endl;  
  56.      return -1;  
  57.    }  
  58.  }  
  59.    
  60.  ListPeople(address_book);  
  61.    
  62.  // Optional:  Delete all globalobjects allocated by libprotobuf.  
  63.  google::protobuf::ShutdownProtobufLibrary();  
  64.    
  65.  return 0;  
  66. }  

注意以上用例中,枚举类型和嵌套结构的用法。

 

以上只是展示了PB的基础用法,其实PB还有许多高级用法:

Advanced Usage

Protocol buffers have uses that gobeyond simple accessors and serialization. Be sure to explore the C++API reference to see what else you can do with them.

One key feature provided by protocolmessage classes is reflection. You can iterate over the fields of amessage and manipulate their values without writing your code against anyspecific message type. One very useful way to use reflection is for convertingprotocol messages to and from other encodings, such as XML or JSON. A moreadvanced use of reflection might be to find differences between two messages ofthe same type, or to develop a sort of "regular expressions for protocolmessages" in which you can write expressions that match certain messagecontents. If you use your imagination, it's possible to apply Protocol Buffersto a much wider range of problems than you might initially expect!

Reflection is provided by the Message::Reflection interface.


相关文章推荐

关于google protocol buffer(PB)的优缺点和一些个人的理解

由于caffe的神经模型通过protocol buffer定义,所以自己搜刮了一些资料,加上自己的一些理解,如下: protocol buffer用一句话概括就是一个数据传输协议,用来将你程序...

Google Protocol Buffer 入门&案例

Protocol buffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了三种语言的实现:java、c++ 和 python,每一种实现都包...
  • ID_CK
  • ID_CK
  • 2014年08月07日 18:08
  • 366

google Protocol Buffer2.5.0.jar

  • 2013年08月20日 21:39
  • 520KB
  • 下载

在python中使用google protocol buffer

今天想要写个python脚本来把我们之前一个项目中使用的gpb格式导出的数据打印出来,由于之前接触python很少,所以基本是从头学起 基本的语法的话我就不说了,我主要记录一下我的几个环境配置 p...
  • kerlw
  • kerlw
  • 2011年05月20日 12:36
  • 3428

Google Protocol Buffer项目无法加载解决方案

今天下载Google Protocol Buffer源码研究时发现打开工程后所有项目都提示无法加载,在输出中找到错误提示是未找到导入的项目“C:\Program Files (x86)\MSBuild...

C++ Class Mapped Google Protocol Buffer Message

摘要 Google Protocol Buffer 是一个优秀的基于二进制的网络消息编解码框架。应用于项目时,可以节省不少的人力资源、开发时间和程序BUG。但其不足之处是protobuf编译器生...

【Google Protocol Buffer】Techniques 中文翻译

Techniques 技巧注:这是本人的翻译,可能不准确,可能有错误,但是基本上可以理解,希望能对大家有所帮助!(转载请注明出处:本文来自learnhard的博客:http://www.codelas...
  • ghlfllz
  • ghlfllz
  • 2011年05月08日 10:45
  • 743

Android NDK下编译google protocol buffer(protobuf)

前面发了一片windows下编译protobuf的文章 后来把生成的.a文件加到android工程后发现不可用 所以只好自己使用NDK编译protobuf了(虽然这样,生成的Inclule的头文件...

Qt Creator中google protocol buffer的快速设置

刚入职,跟一个项目,需要在Qt Creator中用protobuffer,配置了很久,也下载了 Qt Creator用的是Qt_SDK安装的,由于QtCreator默认采用动态编译,而用VS2010...

google Protocol buffer(JAVA接口) 学习

对于google protocol buffer很多人都使用过。最近项目中需要用到序列化传输、解析的工作。打算系统学习一下。 1.在windows环境下的安装步骤 (1)在http://c...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Google Protocol Buffer (PB)简明入门
举报原因:
原因补充:

(最多只允许输入30个字)