Protobuf是Protocol Buffers的簡稱,它是Google公司開發的一種數據描述語言,用於描述一種輕便高效的結構化數據存儲格式,並於2008年對外開源。Protobuf能夠用於結構化數據串行化,或者說序列化。它的設計很是適用於在網絡通信中的數據載體,很適合作數據存儲或 RPC 數據交換格式,它序列化出來的數據量少再加上以 K-V 的方式來存儲數據,對消息的版本兼容性很是強,可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。開發者能夠經過Protobuf附帶的工具生成代碼並實現將結構化數據序列化的功能。php
Protobuf中最基本的數據單元是message,是相似Go語言中結構體的存在。在message中能夠嵌套message或其它的基礎數據類型的成員。html
教程中將描述如何用protocol buffer語言構造你的protocol buffer數據,包括.proto
文件的語法以及如何經過.proto
文件生成數據訪問類。教程中使用的是proto3版本的protocol buffer語言。java
首先看一個簡單的例子,好比說你定義一個搜索請求的message,每個搜索請求會包含一個搜索的字符串,返回第幾頁的結果,以及結果集的大小。在.proto
文件中定義以下:python
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
.proto
文件的第一行指定了使用proto3
語法。若是省略protocol buffer編譯器默認使用proto2
語法。他必須是文件中非空非註釋行的第一行。SearchRequest
定義中指定了三個字段(name/value鍵值對),每一個字段都會有名稱和類型。上面的例子中,全部的字段都是標量類型的兩個整型(page_number和result_per_page)和一個字符串型(query)。不過你還能夠給字段指定複合類型,包括枚舉類型和其餘message類型git
在message定義中每一個字段都有一個惟一的編號,這些編號被用來在二進制消息體中識別你定義的這些字段,一旦你的message類型被用到後就不該該在修改這些編號了。注意在將message編碼成二進制消息體時字段編號1-15將會佔用1個字節,16-2047將佔用兩個字節。因此在一些頻繁使用用的message中,你應該老是先使用前面1-15字段編號。github
你能夠指定的最小編號是1,最大是2E29 - 1(536,870,911)。其中19000到19999是給protocol buffers實現保留的字段標號,定義message時不能使用。一樣的你也不能重複使用任何當前message定義裏已經使用過和預留的字段編號。golang
message的字段必須符合如下規則:objective-c
在單個.proto
文件中能夠定義多個message,這在定義多個相關message時很是有用。好比說,咱們定義SearchRequest
對應的響應message SearchResponse
,把它加到以前的.proto
文件中。編程
message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } message SearchResponse { ... }
.proto
文件中的註釋和C,C++的註釋風格相同,使用// 和 / ... /json
/* SearchRequest represents a search query, with pagination options to * indicate which results to include in the response. */ message SearchRequest { string query = 1; int32 page_number = 2; // Which page number do we want? int32 result_per_page = 3; // Number of results to return per page. }
當你刪掉或者註釋掉message中的一個字段時,將來其餘開發者在更新message定義時就能夠重用以前的字段編號。若是他們意外載入了老版本的.proto
文件將會致使嚴重的問題,好比數據損壞、隱私泄露等。一種避免問題發生的方式是指定保留的字段編號和字段名稱。若是將來有人用了這些字段標識那麼在編譯時protocol buffer的編譯器會報錯。
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
當使用protocol buffer編譯器編譯.proto
文件時,編譯器會根據你在.proto
文件中定義的message類型生成指定編程語言的代碼。生成的代碼包括訪問和設置字段值、格式化message類型到輸出流,從輸入流解析出message等。
.h
and .cc
file from each .proto
, with a class for each message type described in your file..java
file with a class for each message type, as well as a special Builder
classes for creating message class instances..proto
, which is then used with a metaclass to create the necessary Python data access class at runtime..pb.go
file with a type for each message type in your file..rb
file with a Ruby module containing your message types.pbobjc.h
and pbobjc.m
file from each .proto
, with a class for each message type described in your file..cs
file from each .proto
, with a class for each message type described in your file..pb.dart
file with a class for each message type in your file..proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type |
---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | |
float | float | float | float | float32 | Float | float | float | double | |
int32 | 使用可變長度編碼。編碼負數的效率低 - 若是您的字段可能有負值,請改用sint32。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
int64 | 使用可變長度編碼。編碼負數的效率低 - 若是您的字段可能有負值,請改用sint64。 | int64 | long | int/long[3] | int64 | Bignum | long | integer/string[5] | Int64 |
uint32 | 使用可變長度編碼 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
uint64 | 使用可變長度編碼. | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string[5] | Int64 |
sint32 | 使用可變長度編碼。簽名的int值。這些比常規int32更有效地編碼負數。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sint64 | 使用可變長度編碼。簽名的int值。這些比常規int64更有效地編碼負數。 | int64 | long | int/long | int64 | Bignum | long | integer/string[5] | Int64 |
fixed32 | 老是四個字節。若是值一般大於228,則比uint32更有效。 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
fixed64 | 老是八個字節。若是值一般大於256,則比uint64更有效 | uint64 | long | int/long[3] | uint64 | Bignum | ulong | integer/string[5] | Int64 |
sfixed32 | 老是四個字節 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sfixed64 | 老是八個字節 | int64 | long | int/long | int64 | Bignum | long | integer/string[5] | Int64 |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | |
string | 字符串必須始終包含UTF-8編碼或7位ASCII文本,且不能超過232。 | string | String | str/unicode | string | String (UTF-8) | string | string | String |
bytes | 能夠包含不超過232的任意字節序列。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string | List<int> |
當時一個被編碼的message體中不存在某個message定義中的singular字段時,在message體解析成的對象中,相應字段會被設置爲message定義中該字段的默認值。默認值依類型而定:
在定義消息類型時,您可能但願其中一個字段只有一個預約義的值列表中的值。例如,假設您要爲每一個SearchRequest
添加corpus
字段,其中corpus
能夠是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO。您能夠很是簡單地經過向消息定義添加枚舉,併爲每一個可能的枚舉值值添加常量來實現。
在下面的例子中,咱們添加了一個名爲Corpus
的枚舉類型,和一個Corpus
類型的字段:
message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 4; }
如你所見,Corpus
枚舉的第一個常量映射到了0:全部枚舉定義都須要包含一個常量映射到0而且做爲定義的首行,這是由於:
可使用其餘message類型做爲字段的類型,假設你想在每一個SearchResponse
消息中攜帶類型爲Result
的消息,
你能夠在同一個.proto
文件中定義一個Result
消息類型,而後在SearchResponse
中指定一個Result
類型的字段。
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
在上面的示例中,Result
消息類型在與SearchResponse
相同的文件中定義 - 若是要用做字段類型的消息類型已在另外一個.proto
文件中定義,該怎麼辦?
您能夠經過導入來使用其餘.proto文件中的定義。要導入另外一個.proto的定義,請在文件頂部添加一個import語句:
import "myproject/other_protos.proto";
默認狀況下,您只能使用直接導入的.proto
文件中的定義。可是,有時你可能須要將.proto
文件移動到新位置。如今,你能夠在舊位置放置一個虛擬.proto
文件,在文件中使用import public
語法將全部導入轉發到新位置,而不是直接移動.proto
文件並在一次更改中更新全部調用點。任何導入包含import public
語句的proto
文件的人均可以傳遞依賴導入公共依賴項。例如
// new.proto // All definitions are moved here
// old.proto // This is the proto that all clients are importing. import public "new.proto"; import "other.proto";
// client.proto import "old.proto"; // You use definitions from old.proto and new.proto, but not other.proto
編譯器會在經過命令行參數-I
或者--proto-path
中指定的文件夾中搜索.proto
文件,若是沒有提供編譯器會在喚其編譯器的目錄中進行搜索。一般來講你應該將--proto-path
的值設置爲你項目的根目錄,並對全部導入使用徹底限定名稱。
能夠導入proto2版本的消息類型到proto3的消息類型中使用,固然也能夠在proto2消息類型中導入proto3的消息類型。可是proto2的枚舉類型不能直接應用到proto3的語法中。
消息類型能夠被定義和使用在其餘消息類型中,下面的例子裏Result
消息被定義在SearchResponse
消息中
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; }
若是你想在外部使用定義在父消息中的子消息,使用Parent.Type
引用他們
message SomeOtherMessage { SearchResponse.Result result = 1; }
你能夠嵌套任意多層消息
message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 int64 ival = 1; bool booly = 2; } } message MiddleBB { // Level 1 message Inner { // Level 2 int32 ival = 1; bool booly = 2; } } }
若是一個現存的消息類型再也不知足你當前的需求--好比說你但願在消息中增長一個額外的字段--可是仍想使用由舊版的消息格式生成的代碼,不用擔憂!只要記住下面的規則,在更新消息定義的同時又不破壞現有的代碼就很是簡單。
OBSOLETE_
前綴或者將字段編號設置爲reserved
,這些將來其餘用戶就不會意外地重用該字段編號了。未知字段是格式良好的協議緩衝區序列化數據,表示解析器沒法識別的字段。例如,當舊二進制文件解析具備新字段的新二進制文件發送的數據時,這些新字段將成爲舊二進制文件中的未知字段。
最初,proto3消息在解析期間老是丟棄未知字段,但在3.5版本中,咱們從新引入了未知字段的保留以匹配proto2行爲。在版本3.5及更高版本中,未知字段在解析期間保留,幷包含在序列化輸出中。
若是你想建立一個映射做爲message定義的一部分,protocol buffers提供了一個簡易便利的語法
map<key_type, value_type> map_field = N;
key_type
能夠是任意整數或者字符串(除了浮點數和bytes之外的全部標量類型)。注意enum
不是一個有效的key_type
。value_type
能夠是除了映射之外的任意類型(意思是protocol buffers的消息體中不容許有嵌套map)。
舉例來講,假如你想建立一個名爲projects的映射,每個Project
消息關聯一個字符串鍵,你能夠像以下來定義:
map<string, Project> projects = 3;
你能夠在.proto
文件中添加一個可選的package
符來防止消息類型以前的名稱衝突。
package foo.bar; message Open { ... }
在定義message的字段時像以下這樣使用package名稱
message Foo { ... foo.bar.Open open = 1; ... }
package符對生成代碼的影響視編程語言而定
若是想消息類型與RPC(遠程過程調用)系統一塊兒使用,你能夠在.proto
文件中定義一個RPC服務接口,而後protocol buffer編譯器將會根據你選擇的編程語言生成服務接口代碼和stub,加入你要定義一個服務,它的一個方法接受SearchRequest
消息返回SearchResponse
消息,你能夠在.proto
文件中像以下示例這樣定義它:
service SearchService { rpc Search (SearchRequest) returns (SearchResponse); }
與protocol buffer 一塊兒使用的最簡單的RPC系統是gRPC
:一種由Google開發的語言和平臺中立的開源RPC系統。 gRPC
特別適用於protocol buffer,並容許您使用特殊的protocol buffer編譯器插件直接從.proto
文件生成相關的RPC代碼。
若是你不想使用gRPC
,可使用本身實現的RPC系統,更多關於實現RPC系統的細節能夠在Proto2 Language Guide中找到。
Proto3支持JSON中的規範編碼,使得在系統之間共享數據變得更加容易。在下表中逐個類型地列出了編碼規則。
若是JSON編碼數據中缺乏某個值,或者其值爲null,則在解析爲protocol buffer時,它將被解釋爲相應的默認值。若是字段在protocol buffer中具備默認值,則默認狀況下將在JSON編碼的數據中省略該字段以節省空間。編寫編解碼實現能夠覆蓋這個默認行爲在JSON編碼的輸出中保留具備默認值的字段的選項。
proto3 | JSON | JSON example | Notes |
---|---|---|---|
message | object | {"fooBar": v, "g": null,…} |
生成JSON對象。消息字段名稱會被轉換爲小駝峯併成爲JSON對象鍵。若是指定了json_name 字段選項,則將指定的值用做鍵。解析器接受小駝峯名稱(或由json_name 選項指定的名稱)和原始proto字段名稱。 null 是全部字段類型的可接受值,並被視爲相應字段類型的默認值。 |
enum | string | "FOO_BAR" |
使用proto中指定的枚舉值的名稱。解析器接受枚舉名稱和整數值。 |
map<K,V> | object | {"k": v, …} |
全部鍵都將被轉換爲字符串 |
repeated V | array | [v, …] |
null會被轉換爲空列表[] |
bool | true, false | true, false |
|
string | string | "Hello World!" |
|
bytes | base64 string | "YWJjMTIzIT8kKiYoKSctPUB+" |
JSON值將是使用帶填充的標準base64編碼編碼爲字符串的數據。接受帶有/不帶填充的標準或URL安全base64編碼。 |
int32, fixed32, uint32 | number | 1, -10, 0 |
JSON value will be a decimal number. Either numbers or strings are accepted. |
int64, fixed64, uint64 | string | "1", "-10" |
JSON value will be a decimal string. Either numbers or strings are accepted. |
float, double | number | 1.1, -10.0, 0, "NaN","Infinity" |
JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". Either numbers or strings are accepted. Exponent notation is also accepted. |
Any | object |
{"@type": "url", "f": v, … } |
If the Any contains a value that has a special JSON mapping, it will be converted as follows: {"@type": xxx, "value": yyy} . Otherwise, the value will be converted into a JSON object, and the "@type" field will be inserted to indicate the actual data type. |
Timestamp | string | "1972-01-01T10:00:20.021Z" |
Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted. |
Duration | string | "1.000340012s", "1s" |
Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision, followed by the suffix "s". Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision and the suffix "s" is required. |
Struct | object |
{ … } |
Any JSON object. See struct.proto . |
Wrapper types | various types | 2, "2", "foo", true,"true", null, 0, … |
Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer. |
FieldMask | string | "f.fooBar,h" |
See field_mask.proto . |
ListValue | array | [foo, bar, …] |
|
Value | value | Any JSON value | |
NullValue | null | JSON null | |
Empty | object | {} | An empty JSON object |
要生成Java,Python,C ++,Go,Ruby,Objective-C或C#代碼,你須要使用.proto
文件中定義的消息類型,你須要在.proto
上運行protocol buffer編譯器protoc
。若是還沒有安裝編譯器,請下載該軟件包並按照README文件中的說明進行操做。對於Go,還須要爲編譯器安裝一個特殊的代碼生成器插件:你能夠在GitHub上的golang/protobuf項目中找到這個插件和安裝說明。
編譯器像下面這樣喚起:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
IMPORT_PATH
指定了在解析import
命令時去哪裏搜索.proto
文件,若是忽略將在當前工做目錄進行查找,能夠經過傳遞屢次--proto-path
參數來指定多個import目錄,他們將會按順序被編譯器搜索。-I=IMPORT_PATH
是--proto_path
的簡短形式。你能夠提供一個或多個輸出命令:
--cpp_out
generates C++ code in DST_DIR
. See the C++ generated code reference for more.--java_out
generates Java code in DST_DIR
. See the Java generated code reference for more.--python_out
generates Python code in DST_DIR
. See the Python generated code reference for more.--go_out
generates Go code in DST_DIR
. See the Go generated code reference for more.--ruby_out
generates Ruby code in DST_DIR
. Ruby generated code reference is coming soon!--objc_out
generates Objective-C code in DST_DIR
. See the Objective-C generated code reference for more.--csharp_out
generates C# code in DST_DIR
. See the C# generated code reference for more.--php_out
generates PHP code in DST_DIR
. See the PHP generated code reference for more.