Protocol Buffers程式設計指南與應用
Protocol Buffers
Protocol buffers,簡稱Protobuf,是一個獨立於程式語言,獨立於平臺,且可拓展的自動序列化結構資料的機制。
本文件服務於想要在程式中使用Protobuf的JAVA,C++,Python開發者。概述中簡單介紹了Protobuf,接下來將引導讀者一步步開發自己的Protobuf程式。——包括更深一步的protocol buffer encoding。API reference documentation提供三種程式語言語法支援以及在書寫.proto檔案時的程式設計風格推薦。
開發者指導
什麼是protocol buffers?
Protocol buffers提供了一種靈活、高效、自動序列化結構資料的機制,類比於 XML,但是它比 XML 更小、更快、更簡單。僅需要自定義一次你所需的資料格式,然後使用者就可以使用 Protobuf 編譯器自動生成各種語言的原始碼,方便的讀寫使用者自定義的格式化的資料。與語言無關,與平臺無關,還可以在不破壞原資料格式的基礎上,依據原有資料格式,更新現有的資料格式。
Protobuf如何工作
使用者在 .proto 檔案中定義 message 來指定所需要序列化的資料格式。每一個 message 都是一個小的資訊邏輯單元,包含一系列的 name-value 值對。下面舉例來說明一個簡單的 .proto 檔案,它定義了一條包含 Person 資訊的 message。
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 格式非常簡單。每種型別的 message 包含一個或者多個唯一編碼欄位,每個欄位由名稱和值型別組成,值型別可以是數字(整形或者浮點型)、布林值、字串、原始位元組,甚至是其他的 message(如上例所示)。Protobuf 允許 message 中包含 message,以達到分層巢狀。message 中可以定義 optional 欄位、required 欄位和 repeated 欄位。
定義好 message 後,執行 Protobuf 編譯器編譯 .proto 檔案,就可以生成存取資料的相關類。這些類包括簡單的設定及讀取欄位(比如 name() 和 set_name() )的方法,也包括整個資料結構的 message 和原始位元組之間的序列化/反序列化的轉換方法。舉個例子,如果你選擇的是 C++ 語言,執行 Protobuf 編譯器編譯以上 .proto 檔案生成 Person 類。你就能使用這個類去填充、序列化、檢索 Person message。如下程式碼:
Person person;
person.set_name("zebra");
person.set_id(123);
person.set_email("[email protected]");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);
接下來,用如下程式碼來讀取 message:
fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail" << person.email() << endl;
Protobuf 是易於擴充套件的,可以向後相容,我們可以在 message 中新增新欄位,在解析的時候,老版本的資料就會忽略新增加的欄位。因此,如果現有通訊協議使用 Protobuf 做為其資料格式,可以直接擴充套件該通訊協議,而不必擔心著將會破壞現有的程式碼。關於使用 .proto 檔案生成目的碼,後面將會介紹。
為什麼不直接使用XML
Protocol buffers在序列化結構資料上比XML具有更多的優勢:
- 更簡單
- 小3-10倍
- 快20-100倍
- 歧義更少
- 生成的類更容易使用
舉個栗子,如果你想儲存一個人的資訊,包括姓名和郵箱地址,下面是XML的寫法:
<person>
<name>John Doe</name>
<email>[email protected]</email>
</person>
而對應的protocol buffer message(使用protocol buffer 格式)如下:
# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {
name: "John Doe"
email: "[email protected]"
}
當這個message被編碼成protocol buffer二進位制格式時(以上程式碼只是為了方便閱讀和除錯),只有大概28個位元組長,100-200ns即可解析。而XML版本則最少需要69個位元組,並且需要5,000-10,000ns解析時長。
同樣的,操縱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;
然而,protocol buffer也並非總是比XML好——比如說,protocol buffers並不適用於組建基於文字的文件標記,因為我們不能用簡單的方法在文字中插入結構。此外,XML是易讀性,和可編輯性更好的語言;protocol buffers的原始格式則不盡然。XML在某種程度上,是自解釋的,而protocol buffers必須有訊息定義(.proto檔案)才能看出來是什麼意思。
聽起來很不錯,哪怎麼開始呢?
Download the package-包含了適用於Java,Python,和C++語言的原始碼及編譯器,以及I/O類和測試範例。根據README檔案的指示,可以方便的安裝編譯器。
一旦上面的都做完了,請嘗試跟著你所選擇語言的入門指導完整的做一遍,將有助於你通過簡單的例子來了解如何使用protocol buffers。
proto3介紹
最新版本是版本3,它有全新的語言版本:Protocol Buffers 3,和一些已經存在於proto2中的新特性。Proto3簡化了protocol buffer語言,不僅易用性大大提升,而且支援更多程式語言:Java,C++,Python,Java Lite,Ruby,JavaScript,Objective-C,和C#。此外通過最新的Go protoc外掛,還能生成用於Go語言的proto3程式碼,戳此github倉庫golang/protobuf。更多的語言支援正在開發中。
目前我們只建議你在以下情況之一選擇proto3:
- 你的專案所使用的語言是proto3剛剛支援的。
- 希望嘗試使用我們最新的開源軟體gRPC-強烈建議使用proto3來避免相容性問題。
請注意,proto3和proto2的API並不是完全相容的。為了避免相容性問題,我們將在新的protocol buffers發行版本中支援先前的語言。
在當前的發行版本文件中可以瞭解proto3語法的主要變化,proto3的開發者文件即將出版!
(如果proto2和proto3的名字讓你感到不解,這是因為我們當初開源protobuf時,它已經是谷歌的第二個版本,也被稱為proto2。這也是為什麼我們的開原版本號從v2.0.0開始的原因。)
歷史漫談
Protobuf最初是谷歌開發用來解決索引伺服器的請求/響應協議的。在使用Protobuf之前,有一種格式用以處理請求&響應資料的編碼和解碼工作,並且支援多種版本的協議。這使得寫出來的程式碼非常醜陋,比如:
if (version==3) {
...
}else if (version>4) {
if (version==5) {
...
}
...
}
通訊協議因此變得越來越複雜,因為開發者必須確保請求者和接受者互相相容,並且在一方開始使用新協議時,另外一方也要使用新的協議。
Protobuf可用於解決下類問題:
- 自動生成序列化與反序列化程式碼,避免人工處理編碼問題。
- 除了用於短期RPC(遠端過程呼叫)請求之外,開發者還使用該協議作為一種便捷的自描述格式,用於資料持久化。
- RPC伺服器介面可以作為 .proto 檔案來描述,而通過ProtocolBuffer的編譯器生成存根(stub)類供使用者實現伺服器介面。
Protobuf現在已經是Google的混合語言資料標準了,現在已經正在使用的有超過48,162種報文格式定義和超過12,183個 .proto 檔案。他們用於RPC系統和持續資料儲存系統。