Protocol buffers 是一种灵活、高效的序列化结构数据的自动机制--想一想XML,可是它更小,更快,更简单。你只须要把你须要怎样结构化你的数据定义一次,你就能使用特殊生成的代码来方便的用多种语言从一系列数据流中读写你的结构化数据。你甚至不须要中断你用"老"结构编译好的已经部署的程序来更新你的数据结构。html
你在一个名为.proto
的文件里用protocol buffer message 定义你须要序列化数据的结构。每一个protocol buffer message 是一个小的信息逻辑记录,包含了一系列的name-value
对。这里有一个简单的.proto
例子,它定义了一个person
的信息。java
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 type 都有一个或多个独特的属性,每一个属性都有一个name
和一个value
类型,value
类型能够是numbers
( 整数或浮点数),booleans
,strings
,raw bytes
或者其余protocol buffer message types
,容许你以嵌套结构组织你的结构。你能够指定optional
,required
、和repeated
属性。你能够从 Protocol Buffer Language Guide找到更多的关于如何写.proto
文件的信息。python
一旦你定义了你的信息,你就能够运行protocol buffer 编译器来编译你的.proto文件来生成特定语言的数据访问类。这些类提供了简单的对属性的访问函数(例如 name()
和set_name()
)和用来序列化整个结构到raw bytes和从raw bytes 解析出结构的函数。例如,假如你使用的是c++语言,用编译器编译上面那个person
的.proto
文件会生成一个Person
类。你能够在你的应用里用这个类来操纵Person
类的对象。好比,你可能会写一些这样的代码:ios
Person person; person.set_name("John Doe"); person.set_id(1234); person.set_email("jdoe@example.com"); fstream output("myfile", ios::out | ios::binary); person.SerializeToOstream(&output);
而后,你能够经过这样的代码来把你的message读进来:c++
fstream input("myfile", ios::in | ios::binary); Person person; person.ParseFromIstream(&input); cout << "Name: " << person.name() << endl; cout << "E-mail: " << person.email() << endl;
你能够给你的message添加新的属性而不打破向后兼容性(backwards-compatibility);旧的二进制文件仅仅在编译的时候忽略那些新的属性。这样一来,若是你有一个通讯协议使用了protocol buffers当作它传输的数据格式,你能够扩展你的通讯协议而不用担忧破坏现有的代码。编程
你能够在API Reference section找到完整的文档,而且你能够在Protocol buffer encoding找出关于protocol buffer 编码的更多信息.api
Protocol buffers相对XML在序列化数据的时候有不少优点。protocol buffers :数组
例如,倘若你要给person
建模,它有name
和email
属性。在XML里,你须要:数据结构
<person> <name>John Doe</name> <email>jdoe@example.com</email> </person>
用protocol buffer message(在protocol buffer 的text format)是这样的app
# Textual representation of a protocol buffer. # This is *not* the binary format used on the wire. person { name: "John Doe" email: "jdoe@example.com" }
当上面这段代码被编译成binary format
(上面那段text format只是为了方便人类读写编辑的)的时候,它可能只占28字节长,仅仅须要100~200纳秒就能编译。那个XML版本即便移除全部空白也至少须要69字节,而且须要5000~10000纳秒来编译。
一样,操做protocol buffer 更容易:
cout << "Name: " << person.name() << endl; cout << "E-mail:" << person.email() << endl;
然而若是使用XML你须要这样作:
cout << "Name: " << person.getElementsByTagName("name")->item(0)->innerText() << endl; cout << "E-mail: " << person.getElementsByTagName("email")->item(0)->innerText() << endl;
下载地址--这个包包含了完整的c++,python和java语言的编译器源代码,和I/O和测试的类。安装请参阅README。
一旦你安装好了,就能够跟着入门教程来学习了。
这个教程会带你走一遍使用protocol buffer的流程,建立一个简单的实例程序,学会基本的使用方法:
.proto
文件里定义信息格式在这个教程里咱们要建立一个简单的“地址簿”程序来在文件里读写人们的联系人信息。每一个人都有一个name,id,email address和一个联系电话。
你怎样序列化和读取这样一个结构数据呢?这里有三种方法:
protocol buffers 灵活高效,能够解决上述问题。你只须要编写一个.proto
文件来描述你要使用的数据结构。protocol buffer 编译器能够把.proto
文件编译成一个相似于ORM(object relation mapping)实现类的数据访问类,这个类能够把高效的用二进制文件方式存储的数据读写出来。更多的是,它提供了一种向后兼容的扩展机制,使你能够不用担忧兼容性问题来扩展你的数据格式。
为了建立地址簿程序,你须要首先定义一个.proto
文件。定义.proto
文件十分简单: 你添加一个 message
给你想序列化的每一个数据结构 ,而后指定一个 name
和一个type
给message
的每一个属性。下面是一个.proto
文件,定义了地址簿数据结构,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; }
rotobuffer支持的内建数据类型包括bool
,int32
,float
,double
,string
.
注意:message能够嵌套,好比 PhoneNumber 就定义在Person里。
“=1”,“=2”标记了每一个元素的惟一“tag”,这是用在二进制编码里的。使用1-15能够在1个字节里表示这些tag,节省空间,通常把经常使用的须要大量重复的元素使用1-15来编码,把16以上的tag留给不经常使用的元素。
每一个属性必须标记为下列修饰符之一:
required
: 故名思议就是必须提供值的属性,当你把属性设置为required的时候要当心,由于若是之后想修改成其余类型,老的读取类就不兼容新的数据了。optional
: 就是能够不提供值的属性,若是没有提供值,会设置为默认值。默认值能够本身提供,若是没有本身提供默认值,会设置为系统默认值:numeric类型会置为0,字符串置为空串,bool置为false;对于内嵌类型,默认值永远是空实例。repeated
:就是可能重复任意次(包含0次).重复值的顺序会在二进制文件保存下来,能够把重复的属性看作动态大小的数组。注意,因为历史缘由,repeated
数值属性不能有效的被编码成二进制,新的代码可使用[packed=true]
来得到更好的编码效率
例如: repeated int32 samples = 4 [packed=true];
protoc -I=\$SRC_DIR --cpp_out=\$DST_DIR \$SRC_DIR/addressbook.proto
addressbook.pb.h
addressbook.pb.cc
咱们如今来看一些生成的code是什么样的,编译器为咱们生成了什么类和函数呢?
若是咱们打开tutorial.pb.h
,咱们会看到编译器给咱们在.proto
文件里定义的每个message
都生成了一个class,咱们再看Person
类,会发现编译器给message的每一个属性都生成了getters和setters,例如,对于name
,id
,email
,和phone
属性,咱们能够找到这些函数:
// 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();
正如你所能看到的那样,getters和小写属性名同样,setters以set_
开头。还有has_
开头的判断是否设置了值的函数。还有clear_
开头的函数用于清空设置的值。
不一样类型的属性方法不尽相同,例如 id
只有基本的getter,setter方法,而name
,email
等字符串类型的属性多了一个mutable_
开头的getter,和一个多出来的setter。即便尚未设置email
仍然能够调用mutable_email
。它能够自动初始化为一个空字符串。
repeated
属性一样有些特别的方法,例如phone
属性:
_size
(这我的有多少个电话号码)index
访问一个特定的值add_
方法)更多关于编译器生成函数的信息请参看C++ generated code reference
生成的代码包含了一个PhoneType
枚举对应你的.proto
文件里的enum.你能够经过Person::PhoneType
来使用这个枚举,和它的值Person::MOBILE
,Person::HOME
,Person::WORK
(具体实现很复杂,但咱们不须要了解它)
编译器一样生成了一个嵌套类Person::PhoneNumber
。若是查看代码,会发现实际的类是叫作Person_PhoneNumber
,可是使用了一个typedef
来重命名了它,惟一的区别是当你想在另外一个文件里前向声明这个类的时候,必须使用Person_PhoneNumber
来前向声明它。
每一个message类还包含了一些其余方法来使你能检查或者操做整个message,包括:
bool IsInitialized() const
;: checks if all the required fields have been set.string DebugString() const
;: returns a human-readable representation of the message, particularly useful for debugging.void CopyFrom(const Person& from)
;: overwrites the message with the given message's values.void Clear()
;: clears all the elements back to the empty state.最终,每一个protocol buffer class使用读写方法来解析和序列化message到二进制文件里,这些方法包括:
bool SerializeToString(string* output) const
;: 序列化一个message而且把字节文件存储到string里,这里使用string仅仅是为了把它当作一个方便的容器.bool ParseFromString(const string& data)
;: 从指定的string里解析messagebool SerializeToOstream(ostream* output) const
;: 把message写到指定的c++`ostream`里。bool ParseFromIstream(istream* input)
;: 从指定的c++istream
读取message查看Message API获取更详细内容.
如今,让咱们试着使用编译器为咱们生成的类。咱们让咱们的地址簿程序作的第一件事情是把一我的的我的信息写到地址簿文件里。咱们须要生成一个该类的实例而后把它写入到输出流里。
这里有一个实例程序:
#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; }
注意代码中的GOOGLE_PROTOBUF_VERIFY_VERSION
宏,在使用c++ Protocol Buffer 以前执行这个宏是一个好的习惯(尽管不是强制要求的)。它会验证你是否连接了正确的库,防止你连接版本不匹配的库。
注意代码中的ShutdownProtobufLibrary()
,它会清楚全部protocol buffer libarary分配的全局对象。一般这是不须要的,由于这个进程老是会退出,系统会接管剩下的内存。可是,若是你使用了一个内存泄露检查工具,好比valgrand
之类的,这类工具会要求你把全部分配的内存释放掉,或者你在写一个库文件,这个库文件会被同一个进程加载和卸载屡次,这两种状况你就须要清理全部东西。
这是一个从二进制文件读取地址簿的例子:
#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; }
当一段时间以后你须要在你发布使用你的protocol buffer后改进你的protocol buffer定义。若是你但愿你的新buffer可以向前兼容,而你的老buffer能向后兼容,那么你就须要遵照下面这几个规则:
tag
数字required
属性repeated
或者optional
属性repeated
或者optional
属性,可是必须使用新tag number转自:https://zybuluo.com/anboqing/note/263467
参考:http://www.cnblogs.com/shitouer/archive/2013/04/08/google-protocol-buffers-overview.html