Protocol Buffer是Google 的一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。
// 定义一个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、每个字段都必须使用如下标示符
5、enum是枚举类型定义的关键字,0和1表示枚举值所对应的实际整型值,和C/C++一样,可以为枚举值指定任意整型值,而无需总是从0开始定义。
6、可以在同一个.proto文件中定义多个message,这样便可以很容易的实现嵌套消息的定义。Protocol Buffer提供了另外一个关键字import,这样我们便可以将很多通用的message定义在同一个.proto文件中,而其他消息定义文件可以通过import的方式将该文件中定义的消息包含进来,如:
import "myproject/CommonMessages.proto"
1、在每个消息中必须至少留有一个required类型的字段。
2、每个消息中可以包含0个或多个optional类型的字段。
3、repeated表示的字段可以包含0个或多个数据。
4、如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段。
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限定符也是相互兼容的。
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文件,实现产生的类。
经过编译后我们能够得到下面这些消息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 ();
每个消息都会包含一系列其他方法,允许你检查或者操作整个消息:
bool IsInitialized () const ; // 检查是否所有required field被设置 string DebugString () const ; // 返回一个有关消息的可读描述,对于调试很有用 void CopyFrom (const Person & from ); // 用给定的消息值来重写消息 void Clear (); // 将所有元素清空到empty state
每个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.
#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; }
#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; }