ProtoBuf是一種靈活高效的獨立於語言平臺的結構化數據表示方法,可用於表示通訊協議和數據存儲等各方面,與XML相比,ProtoBuF更小更快更簡單。你能夠用定義本身ProtoBuf的數據結構,用ProtoBuf編譯器生成特定語言的源代碼,(如C++,Java,Python等,目前 ProtoBuf對主流的編程語言都提供了支持)方便的進行序列化和反序列化。html
protobuf是google旗下的一款平臺無關,語言無關,可擴展的序列化結構數據格式。因此很適合用作數據存儲和做爲不一樣應用,不一樣語言之間相互通訊的數據交換格式,只要實現相同的協議格式即同一proto文件被編譯成不一樣的語言版本,加入到各自的工程中去。這樣不一樣語言就能夠解析其餘語言經過 protobuf序列化的數據。目前官網提供了C++、Python、JAVA、GO等語言的支持。linux
1. ProtoBuf安裝
protobuf包含兩部分,一個是protobuf編譯器,用於解析編譯.proto文件爲.pb.cc和.pb.h文件,另外一部分是protobuf runtime。不一樣編程語言有不一樣的runtime,主流語言基本都支持,如C++,Java,Python,Go等。ios
1.1 protoc安裝
可直接從https://github.com/protocolbuffers/protobuf/releases安裝穩定版本。c++
注:releases包含protoc和runtime兩部分,分別屬於不一樣安裝包。C++版本源碼包含protoc,不須要另外安裝protoc,但其餘語言都須要單獨安裝protoc。git
1.2 protobuf runtime安裝
以C++爲例,參考:https://github.com/protocolbuffers/protobuf/tree/master/src。 github
git clone https://github.com/protocolbuffers/protobuf.git cd protobuf git submodule update --init --recursive ./autogen.sh ./configure make make check sudo make install sudo ldconfig # refresh shared library cache.
注:編譯的庫文件位於src目錄下。編程
2. ProtoBuf使用
2.1應用protobuf步驟
-
在.proto文件中定義message格式。ubuntu
-
用protoc編譯.proto文件,生成.pb.cc和.pb.h文件。數據結構
-
運用C++ protocol buffer API讀寫messages。編程語言
2.2應用protobuf示例
在一個後綴名爲.pro的文件中定義你所需的任何須要序列化和反序列的結構,相似於中咱們C/C++語言中的結構體定義,也相似於JSON類定義,ProtoBuf文件的每個屬性都是一個鍵值對,引用谷歌官方的例子說明https://developers.google.cn/protocol-buffers/docs/cpptutorial,一個包含Person信息的.pro文件。
syntax = "proto2"; 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 people = 1; }
.pro 文件的每個屬性都是一個包含類型和名稱的對,protobuf的類型能夠是string、int、bool,或者是其餘ProtoBuf源文件中定位的類型等。還需指定該屬性的一些特殊字段,好比optional表示該屬性是可選的,required表示該屬性是必須的,repeated表示該屬性是一個list的集合(不知道用list相似形容是否合適,就差很少意思吧,包含1個或多個元素),包含了若干個相同類型的值,不過在protoBuf 3中,optional和required都不須要的了,若是配了兩個,protoBuf 編譯器還會報錯,可是repeated的仍是保留的,用來講明該字段是一個list。
As you can see, each field in the message definition has a unique number. These numbers are used to identify your fields in the message binary format, and should not be changed once your message type is in use. Note that field numbers in the range 1 through 15 take one byte to encode, including the field number and the field's type (you can find out more about this in Protocol Buffer Encoding). Field numbers in the range 16 through 2047 take two bytes. So you should reserve the field numbers 1 through 15 for very frequently occurring message elements. Remember to leave some room for frequently occurring elements that might be added in the future. https://developers.google.cn/protocol-buffers/docs/proto
.protoBuf定義完之後,就可使用ProtoBuf編譯器進行編譯,編譯時須要指定你須要生成的代碼的語言類型。.pro文件編譯成功後會生成*.pro.pb.hh 和*.pro.pb.cc兩個文件,*和.pro源文件名稱相同。在生成的文件中會生成一個你定義的消息類型的的類以及讀取和設置該消息類型各個屬性的 set和Get方法等,好比上例中會生成一個Person類,和name(),set_name()這樣的成員方法,以及序列化和反序列的方法,你能夠用 這些方法方便的讀取和設置相應的屬性,以及序列化和反序列化方法。
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
寫message
#include <iostream> #include <fstream> #include <string> #include "addressbook.pb.h" using namespace std; //using namespace caffe; 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; } } } int main(int argc, char **argv) { GOOGLE_PROTOBUF_VERIFY_VERSION; if(argc != 2){ cerr <<"Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; return -1; } tutorial::AddressBook 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; } PromptForAddress(address_book.add_people()); fstream output(argv[1], ios::out | ios::trunc | ios::binary); if(!address_book.SerializeToOstream(&output)){ cerr << "Failed to write address book." << endl; return -1; } google::protobuf::ShutdownProtobufLibrary(); return 0; }
編譯命令:
g++ write.cpp addressbook.pb.cc -o write `pkg-config --cflags --libs protobuf` -std=c++11
讀message
#include <iostream> #include <fstream> #include <string> #include "addressbook.pb.h" using namespace std; void PrintInfo(const tutorial::AddressBook & address_book) { for(int i = 0; i < address_book.people_size(); i++){ const tutorial::Person& person = address_book.people(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; } } } int main(int argc, char *argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; if(argc != 2){ cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; return -1; } tutorial::AddressBook address_book; fstream input(argv[1], ios::in | ios::binary); if(!address_book.ParseFromIstream(&input)){ cerr << "Failed to parse address book." << endl; return -1; } PrintInfo(address_book); google::protobuf::ShutdownProtobufLibrary(); return 0; }
編譯命令:
g++ read.cpp addressbook.pb.cc -o read `pkg-config --cflags --libs protobuf` -std=c++11
執行
$ ./write test_addr test_addr: File not found. Creating a new file. Enter person ID number:10000 Enter name :wang Enter email address (blank for none): 123456@163.com Enter a phone number ( or leave blank to finish):18000000000 Is this a mobile, home, or work phone?home Enter a phone number ( or leave blank to finish): $ cat test_addr * wang▒N123456@163.com" $ ./read Usage: ./read ADDRESS_BOOK_FILE wang@ubuntu-wang:~/repository/protoc/example_me$ ./read test_addr Person ID: 10000 Name: wang E-mail address: 123456@163.com Home phone #: 18000000000
3. ProtoBuf兼容性
ProtoBuf的一個亮點是提供了向前和向後的兼容性,你能夠再你定義的消息格式中增長字段或者刪除字段,當消息由一個增長字段後的系統向一個老 版本系統發送消息時,老版本解析該消息格式後會直接忽略新增的字段,而當消息從老版本發向新版本時,新版本解析該消息時會將新增的字段設置爲默認值,因此 你不須要擔憂在系統中增長消息字段後的兼容問題以及版本升級配套問題。
參考: