转载

ProtoBuffer使用详解

Protocol Buffer是Google 的一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。

定义一个Protocol Buffer消息

使用实例

// 定义一个addressbook.proto
package tutorial;

message Person {
 required string name = 1;
 required int32 id = 2;
 optional string email = 3;

 enum PhoneType {
 MOBILE = 0;
 HOME = 1;
 WORK = 2;
 }

 message PhoneNumber{
 required string number = 1;
 optional PhoneType type = 2[ default = HOME];
 }

 repeated PhoneNumber phone = 4;
}

message AddressBook {
 repeated Person person = 1;
}

细节解释

1、package declaration为了阻止不同工程间的naming conflicts,这儿的tutorial相当于namespace。

2、message是一个包含若干类型字段的集合,可以使用bool、int32、float、double和string类型。可以内嵌message集合,类似于struct。

3、“=1”、“2”记号标识在二进制编码中类型字段的独特Tag,表示不同的字段在序列化后的二进制数据中的布局位置。

Tag number 1-15相对于更高的数字,少用了一个字节,所以可以使用1-15的Tag作为commonly used的repeated elements,16或者更高的Tag留给less-commonly use留给optional elements。

4、每个字段都必须使用如下标示符

  • required:字段值必须被提供,否则消息会被认为uninitialized。
  • optional:字段值可选
  • repeated:字段也许会被重复任何次数(包括0次)。可以将repeated field看做动态大小数组。

5、enum是枚举类型定义的关键字,0和1表示枚举值所对应的实际整型值,和C/C++一样,可以为枚举值指定任意整型值,而无需总是从0开始定义。

6、可以在同一个.proto文件中定义多个message,这样便可以很容易的实现嵌套消息的定义。Protocol Buffer提供了另外一个关键字import,这样我们便可以将很多通用的message定义在同一个.proto文件中,而其他消息定义文件可以通过import的方式将该文件中定义的消息包含进来,如:

import "myproject/CommonMessages.proto"

限定符(required/optional/repeated)的基本规则

1、在每个消息中必须至少留有一个required类型的字段。

2、每个消息中可以包含0个或多个optional类型的字段。

3、repeated表示的字段可以包含0个或多个数据。

4、如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段。

Protocol Buffer消息升级原则

1、不要修改已经存在字段的标签号。

2、任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。

3、在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。

4、int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。

5、optional和repeated限定符也是相互兼容的。

编译你的Protocol Buffers

编译方法

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

注:$SRC_DIR为Source Directory,$DST_DIR为Destination Directory,编译后,Destination Directory将会有以下两个文件

addressbook.pb.h:头文件,声明产生的类
addressbook.pb.cc:cpp文件,实现产生的类。

ProtoBuffer API

经过编译后我们能够得到下面这些消息API函数

// 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 ();

// phone
inline int phone_size () const ;
inline void clear_phone ();
inline const ::google ::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const ;
inline ::google ::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone();
inline const ::tutorial::Person_PhoneNumber& phone(int index) const;
inline ::tutorial::Person_PhoneNumber* mutable_phone (int index );
inline ::tutorial::Person_PhoneNumber* add_phone ();

标准Message方法

每个消息都会包含一系列其他方法,允许你检查或者操作整个消息:

bool IsInitialized () const ; // 检查是否所有required field被设置
string DebugString () const ; // 返回一个有关消息的可读描述,对于调试很有用
void CopyFrom (const Person & from ); // 用给定的消息值来重写消息
void Clear (); // 将所有元素清空到empty state

使用你的ProtocBuffer

解析和序列化(Parsing and Serialization)

每个Protocol buffer类都有若干函数,这些函数能使用Protocol buffer binary format,来写入和读取你所选择的信息。

bool SerializeToString (string * output ) const ; // serializes the message and stores the bytes in the given string. Note that the bytes are binary, not text; we only use the string class as a convenient container.
bool ParseFromString (const string & data ); // parses a message from the given string.
bool SerializeToOstream (ostream * output ) const ; // writes the message to the given C++ ostream.
bool ParseFromIstream (istream * input ); // parses a message from the given C++ istream.

Writing A Message

#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_phone();
 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_person());

 {
 // 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;
}

Reading A Message

#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std ;

// Iterates though all people in the AddressBook and prints info about them.
void ListPeople (const tutorial ::AddressBook & address_book ) {
 for ( int i = 0; i < address_book.person_size(); i ++) {
 const tutorial::Person& person = address_book.person (i );

 cout << "Person ID: " << person .id () << endl;
 cout << " Name: " << person .name () << endl;
 if ( person.has_email()) {
 cout << " E-mail address: " << person .email() << endl;
 }

 for ( int j = 0; j < person.phone_size(); j++) {
 const tutorial::Person::PhoneNumber& phone_number = person.phone(j );

 switch ( phone_number.type ()) {
 case tutorial::Person::MOBILE:
 cout << " Mobile phone #: ";
 break;
 case tutorial::Person::HOME:
 cout << " Home phone #: ";
 break;
 case tutorial::Person::WORK:
 cout << " Work phone #: ";
 break;
 }
 cout << phone_number.number() << endl ;
 }
 }
}

// Main function: Reads the entire address book from a file and prints all
// the information inside.
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 (! address_book.ParseFromIstream(&input )) {
 cerr << "Failed to parse address book." << endl;
 return -1;
 }
 }

 ListPeople(address_book );

 // Optional: Delete all global objects allocated by libprotobuf.
 google::protobuf::ShutdownProtobufLibrary();

 return 0;
}
原文  http://freehacker.cn/utility/protobuffer/
正文到此结束
Loading...