一、什么是protocol buffers
Protocol buffers是一個靈活的、高效的、自動化的用于對結構化數據進行序列化的協議,與XML相比,Protocol buffers序列化后的碼流更小、速度更快、操作更簡單。你只需要將要被序列化的數據結構定義一次(譯注:使用.proto文件定義),便可以使用特別生成的源代碼(譯注:使用protobuf提供的生成工具)輕松的使用不同的數據流完成對這些結構數據的讀寫操作,即使你使用不同的語言(譯注:protobuf的跨語言支持特性)。你甚至可以更新你的數據結構的定義(譯注:就是更新.proto文件內容)而不會破壞依賴“老”格式編譯出來的程序。
二、protocol buffers的工作流程
首先,你需要通過在.proto文件中定義protocol buffer的message類型來指定你想要序列化的數據結構,每一個protocol buffer message是一個邏輯上的信息記錄,它包含一系列的鍵值對。這里展示一個最基本的.ptoto文件的例子,它定義了一個包含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類型都有一個或多個帶有唯一編號的字段,每一個字段有一個字段名和一個字段類型,字段類型可以是數值類型(比如整形或浮點型)、booleans(布爾類型)、strings(字符串類型)、raw bytes、甚至(正如上面的例子)還可以是其他的protocol buffer message類型,這允許你可以分層次的組織你的數據結構。你可以單獨指定每一個字段為optional fields(可選字段)、required fields(必須字段)、repeated fields(可重復字段)。下一篇博文將會對.proto文件進行更詳細的描述。
一旦定義了你的message,你就可以根據你所使用的語言(譯注:如JAVA、C++、Python等)使用protocol buffer提供的編譯工具編譯.proto文件生成數據訪問類。這些類為每一個字段都提供了簡單的訪問器(比如name()和set_name()),同時還提供了將整個結構化數據序列化為原始字節數據以及從原始字節數據反序列化為結構化數據的方法(譯注:C++中稱之為函數)。例如,如果你使用的語言是C++,運行編譯器編譯上述的例子將生成一個名為Person的類,在你的應用程序中你可以使用這個類來填充、序列化和反序列化Person protocol buffer messages。之后你可能會寫下如下類似的代碼(譯注:序列化):
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讀回(譯注:反序列化):
fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;
你可以向你的message中添加新的字段而不會破壞前向兼容性;在解析時舊的二進制文件會簡單的忽略掉新字段,所以,如果你的通信協議中使用protocol buffers作為數據交換格式,那么你可以擴展你的協議而不用擔心會打亂現有的代碼。
三、為什么不使用XML?
相對于XML,protocol buffers在序列化結構數據時擁有許多先進的特性:
1、更簡單
2、序列化后字節占用空間比XML少3-10倍
3、序列化的時間效率比XML快20-100倍
4、具有更少的歧義性
5、自動生成數據訪問類方便應用程序的使用
舉個例子,如果你想描述一個具有name和email的person數據結構,在XML中,你需要這樣做:
<person><name>John Doe</name><email>jdoe@example.com</email></person>
然而,在protocol buffers的message中(protocol buffers的文本格式)你需要這樣做:
# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {name: "John Doe"email: "jdoe@example.com"
}
當這個message被編碼成protocol buffer的二進制格式(上述的文本格式只是為了方便閱讀、調試和編輯),它將可能占用28個字節長度并且僅需要100-200納秒的解析時間。相比,XML版本的則至少需要占用69字節的空間(這是在移除XML中的空格、換行之后),同時,將耗費大約5000-10000納秒的解析時間。
除此之外,手動操作protocol buffer更為方便,例如如下C++代碼:
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;
事物總有兩面性,和XML相比protocol buffers并不總是更好的選擇,例如,protocol buffers并不適合用來描述一個基于文本的標記型文檔(比如HTML),因為你無法輕易的交錯文本的結構。另外,XML具有很好的可讀性和可編輯性;而protocol buffers,至少在它們的原生形式上并不具備這個特點。XML同時也是可擴展、自描述的。而一個protocol buffer只有在具有message 定義(在.proto文件中定義)時才會有意義。
四、如何開始使用protocol buffers?
首先,可以在這里下載安裝包或者源碼包
https://developers.google.com/protocol-buffers/docs/downloads#release-packages
這包含了針對JAVA、Python和C++編譯器的完整源碼,同時包含了你所需要的I/O和測試類。為了完成編譯和安裝,請參照README文件。
一旦你完成了編譯和安裝,那么就可以開始使用protocol buffers了,后續的博文將會對C++和JAVA語言的具體使用細節進行闡述。
五、proto3介紹
我們最新的版本version 3 alpha release引進了一個新的語言版本–Protocol Buffers version 3 (稱之為proto3),它在我們現存的語言版本(proto2)上引進了一些新特性。proto3簡化了protocol buffer language,這使其可以更便于使用和支持更多的編程語言:我們現在的alpha release版本可以讓你能產生JAVA、C++、Pthyon、JavaNano、Ruby、Objective-C和C#版本的protocol buffer code,不過可能有時會有一些局限性。另外,你可以使用最新的Go protoc插件來產生Go語言版本的proto3 code,這可以從golang/protobuf Github repository獲取。
我們現在只推薦你使用proto3:
1、如果你想嘗試在我們新支持的語言中使用protocol buffers
2、如果你想嘗試我們最新開源的RPC實現gRPC(目前仍處于alpha release版本),我們建議你為所有的gRPC 服務器和客戶端都使用proto3以避免兼容性問題。
注意兩個版本的語言APIs并不是完全兼容的,為了避免給原來的用戶造成不便,我們將會繼續維護之前的那個版本(譯注:proto2)。
六、最后說一點歷史
Protocol buffers最初被Google開發用來作為處理索引服務器的request/response協議。在protocol buffers誕生之前,有一個需要手動編碼/解碼requests、responses的協議,這個協議支持一個數字版本號,這導致了一個非常丑陋的代碼,如下所示:
if (version == 3) {...} else if (version > 4) {if (version == 5) {...}...}
很顯然的,格式化的協議也導致了復雜的新版本推出問題,因為開發人員必須確保所有服務器請求的發起者和實際的請求處理者之間都要理解新的協議。
Protocol buffers就是用來解決這些問題的:
1、可以很容易的插入新字段,中間的服務器可以簡單的解析它而不需要了解所有字段。
2、格式更具有自描述性,可以被不同的語言處理(比如JAVA、C++、Python等)。
3、自動產生序列化和反序列化代碼從而避免了手動解析。
4、除了應用在具有短暫生命周期的RPC請求中,人們開始使用protocol buffers 作為一種便利的自描述格式來存儲數據(比如在Bigtable中)。
5、服務器的RPC接口開始被聲明為協議文件的一部分,通過protocol 編譯器產生stub類,該類可以被用戶根據實際實現的服務器接口進行重寫。