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