1. 程式人生 > >protobuf協議語言指南

protobuf協議語言指南

Protobuf語言指南

定義一個訊息(message)型別

標量值型別

Optional 的欄位及預設值

列舉

使用其他訊息型別

巢狀型別

更新一個訊息型別

擴充套件

包(package)

定義服務(service)

選項(option)

生成訪問類

本指南描述了怎樣使用protocolbuffer語言來構造你的protocol buffer資料,包括.proto檔案語法以及怎樣生成.proto檔案的資料訪問類。

定義一個訊息型別

先來看一個非常簡單的例子。假設你想定義一個“搜尋請求”的訊息格式,每一個請求含有一個查詢字串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以採用如下的方式來定義訊息型別的.proto檔案了:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3;

}

SearchRequest訊息格式有3個欄位,在訊息中承載的資料分別對應於每一個欄位。其中每個欄位都有一個名字和一種型別。

Ø  指定欄位型別

在上面的例子中,所有欄位都是標量型別:兩個整型(page_number和result_per_page),一個string型別(query)。當然,你也可以為欄位指定其他的合成型別,包括列舉(enumerations

)或其他訊息型別。

Ø   分配標識號

正如上述檔案格式,在訊息定義中,每個欄位都有唯一的一個識別符號。這些識別符號是用來在訊息的二進位制格式中識別各個欄位的,一旦開始使用就不能夠再改變。注:[1,15]之內的標識號在編碼的時候會佔用一個位元組。[16,2047]之內的標識號則佔用2個位元組。所以應該為那些頻繁出現的訊息元素保留[1,15]之內的標識號。切記:要為將來有可能新增的、頻繁出現的標識號預留一些標識號。

最小的標識號可以從1開始,最大到229 - 1, or 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto檔案中使用這些預留標識號,編譯時就會報警。

Ø  指定欄位規則

所指定的訊息欄位修飾符必須是如下之一:

²  required:一個格式良好的訊息一定要含有1個這種欄位。表示該值是必須要設定的;

²  optional:訊息格式中該欄位可以有0個或1個值(不超過1個)。

²  repeated:在一個格式良好的訊息中,這種欄位可以重複任意多次(包括0次)。重複的值的順序會被保留。表示該值可以重複,相當於java中的List。

由於一些歷史原因,基本數值型別的repeated的欄位並沒有被儘可能地高效編碼。在新的程式碼中,使用者應該使用特殊選項[packed=true]來保證更高效的編碼。如:

repeated int32 samples = 4 [packed=true];

required是永久性的:在將一個欄位標識為required的時候,應該特別小心。如果在某些情況下不想寫入或者傳送一個required的欄位,將原始該欄位修飾符更改為optional可能會遇到問題——舊版本的使用者會認為不含該欄位的訊息是不完整的,從而可能會無目的的拒絕解析。在這種情況下,你應該考慮編寫特別針對於應用程式的、自定義的訊息校驗函式。Google的一些工程師得出了一個結論:使用required弊多於利;他們更願意使用optional和repeated而不是required。當然,這個觀點並不具有普遍性。

Ø   新增更多訊息型別

在一個.proto檔案中可以定義多個訊息型別。在定義多個相關的訊息的時候,這一點特別有用——例如,如果想定義與SearchResponse訊息型別對應的回覆訊息格式的話,你可以將它新增到相同的.proto檔案中,如:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3;

}

message SearchResponse {

 …

}

Ø  添加註釋

向.proto檔案添加註釋,可以使用C/C++/java風格的雙斜槓(//) 語法格式,如:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;// 最終返回的頁數

  optional int32 result_per_page = 3;// 每頁返回的結果數

}

Ø  從.proto檔案生成了什麼?

當用protocolbuffer編譯器來執行.proto檔案時,編譯器將生成所選擇語言的程式碼,這些程式碼可以操作在.proto檔案中定義的訊息型別,包括獲取、設定欄位值,將訊息序列化到一個輸出流中,以及從一個輸入流中解析訊息。

²  對C++來說,編譯器會為每個.proto檔案生成一個.h檔案和一個.cc檔案,.proto檔案中的每一個訊息有一個對應的類。

²  對Java來說,編譯器為每一個訊息型別生成了一個.java檔案,以及一個特殊的Builder類(該類是用來建立訊息類介面的)。

²  對Python來說,有點不太一樣——Python編譯器為.proto檔案中的每個訊息型別生成一個含有靜態描述符的模組,,該模組與一個元類(metaclass)在執行時(runtime)被用來建立所需的Python資料訪問類。

標量數值型別

一個標量訊息欄位可以含有一個如下的型別——該表格展示了定義於.proto檔案中的型別,以及與之對應的、在自動生成的訪問類中定義的型別:

.proto型別

Java 型別

C++型別

備註

double

double

double

float

float

float

int32

int

int32

使用可變長編碼方式。編碼負數時不夠高效——如果你的欄位可能含有負數,那麼請使用sint32。

int64

long

int64

使用可變長編碼方式。編碼負數時不夠高效——如果你的欄位可能含有負數,那麼請使用sint64。

sint32

int

int32

使用可變長編碼方式。有符號的整型值。編碼時比通常的int32高效。

sint64

long

int64

使用可變長編碼方式。有符號的整型值。編碼時比通常的int64高效。

fixed32

int

uint32

總是4個位元組。如果數值總是比總是比228大的話,這個型別會比uint32高效。

fixed64

long

uint64

總是8個位元組。如果數值總是比總是比256大的話,這個型別會比uint64高效。

sfixed32

int

int32

總是4個位元組。

fixed64

long

int64

總是8個位元組。

bool

boolean

bool

string

String

string

一個字串必須是UTF-8編碼或者7-bit ASCII編碼的文字。

bytes

ByteString

string

可能包含任意順序的位元組資料。

Optional的欄位和預設值

如上所述,訊息描述中的一個元素可以被標記為“可選的”(optional)。一個格式良好的訊息可以包含0個或一個optional的元素。當解析訊息時,如果它不包含optional的元素值,那麼解析出來的物件中的對應欄位就被置為預設值。預設值可以在訊息描述檔案中指定。例如,要為SearchRequest訊息的result_per_page欄位指定預設值10,在定義訊息格式時如下所示:

optional int32 result_per_page = 3 [default = 10];

如果沒有為optional的元素指定預設值,就會使用與特定型別相關的預設值:對string來說,預設值是空字串。對bool來說,預設值是false。對數值型別來說,預設值是0。對列舉來說,預設值是列舉型別定義中的第一個值。

列舉

當需要定義一個訊息型別的時候,可能想為一個欄位指定某“預定義值序列”中的一個值。例如,假設要為每一個SearchRequest訊息新增一個corpus欄位,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。其實可以很容易地實現這一點:通過向訊息定義中新增一個列舉(enum)就可以了。一個enum型別的欄位只能用指定的常量集中的一個值作為其值(如果嘗試指定不同的值,解析器就會把它當作一個未知的欄位來對待)。在下面的例子中,在訊息格式中添加了一個叫做Corpus的列舉型別——它含有所有可能的值——以及一個型別為Corpus的欄位:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3 [default = 10];

  enum Corpus {

    UNIVERSAL = 0;

    WEB = 1;

    IMAGES = 2;

    LOCAL = 3;

    NEWS = 4;

    PRODUCTS = 5;

    VIDEO = 6;

  }

  optional Corpus corpus = 4 [default = UNIVERSAL];

}

列舉常量必須在32位整型值的範圍內。因為enum值是使用可變編碼方式的,對負數不夠高效,因此不推薦在enum中使用負數。如上例所示,可以在一個訊息定義的內部或外部定義列舉——這些列舉可以在.proto檔案中的任何訊息定義裡重用。當然也可以在一個訊息中宣告一個列舉型別,而在另一個不同的訊息中使用它——採用MessageType.EnumType的語法格式。

當對一個使用了列舉的.proto檔案執行protocol buffer編譯器的時候,生成的程式碼中將有一個對應的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對Python來說),它被用來在執行時生成的類中建立一系列的整型值符號常量(symbolic constants)。

關於如何在你的應用程式的訊息中使用列舉的更多資訊,請檢視所選擇的語言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。

使用其他訊息型別

你可以將其他訊息型別用作欄位型別。例如,假設在每一個SearchResponse訊息中包含Result訊息,此時可以在相同的.proto檔案中定義一個Result訊息型別,然後在SearchResponse訊息中指定一個Result型別的欄位,如:

message SearchResponse {

  repeated Result result = 1;

}

message Result {

  required string url = 1;

  optional string title = 2;

  repeated string snippets = 3;

}

Ø  匯入定義

在上面的例子中,Result訊息型別與SearchResponse是定義在同一檔案中的。如果想要使用的訊息型別已經在其他.proto檔案中已經定義過了呢?

 你可以通過匯入(importing)其他.proto檔案中的定義來使用它們。要匯入其他.proto檔案的定義,你需要在你的檔案中新增一個匯入宣告,如:

import "myproject/other_protos.proto";

protocol編譯器就會在一系列目錄中查詢需要被匯入的檔案,這些目錄通過protocol編譯器的命令列引數-I/–import_path指定。如果不提供引數,編譯器就在其呼叫目錄下查詢。

巢狀型別

你可以在其他訊息型別中定義、使用訊息型別,在下面的例子中,Result訊息就定義在SearchResponse訊息內,如:

message SearchResponse {

  message Result {

    required string url = 1;

    optional string title = 2;

    repeated string snippets = 3;

  }

  repeated Result result = 1;

}

如果你想在它的父訊息型別的外部重用這個訊息型別,你需要以Parent.Type的形式使用它,如:

message SomeOtherMessage {

  optional SearchResponse.Result result = 1;

}

當然,你也可以將訊息巢狀任意多層,如:

message Outer {                  // Level 0

  message MiddleAA {  // Level 1

    message Inner {   // Level 2

      required int64 ival = 1;

      optional bool  booly = 2;

    }

  }

  message MiddleBB {  // Level 1

    message Inner {   // Level 2

      required int32 ival = 1;

      optional bool  booly = 2;

    }

  }

}

Ø 

注:該特性已被棄用,在建立新的訊息型別的時候,不應該再使用它——可以使用巢狀訊息型別來代替它。

“組”是指在訊息定義中巢狀資訊的另一種方法。比如,在SearchResponse中包含若干Result的另一種方法是 :

message SearchResponse {

  repeated group Result = 1 {

    required string url = 2;

    optional string title = 3;

    repeated string snippets = 4;

  }

}

一個“組”只是簡單地將一個巢狀訊息型別和一個欄位捆綁到一個單獨的宣告中。在程式碼中,可以把它看成是含有一個Result型別、名叫result的欄位的訊息(後面的名字被轉換成了小寫,所以它不會與前面的衝突)。

因此,除了資料傳輸格式不同之外,這個例子與上面的SearchResponse例子是完全等價的。

更新一個訊息型別

如果一個已有的訊息格式已無法滿足新的需求——如,要在訊息中新增一個額外的欄位——但是同時舊版本寫的程式碼仍然可用。不用擔心!更新訊息而不破壞已有程式碼是非常簡單的。在更新時只要記住以下的規則即可。

²  不要更改任何已有的欄位的數值標識。

²  所新增的任何欄位都必須是optional或repeated的。這就意味著任何使用“舊”的訊息格式的程式碼序列化的訊息可以被新的程式碼所解析,因為它們不會丟掉任何required的元素。應該為這些元素設定合理的預設值,這樣新的程式碼就能夠正確地與老程式碼生成的訊息互動了。類似地,新的程式碼建立的訊息也能被老的程式碼解析:老的二進位制程式在解析的時候只是簡單地將新欄位忽略。然而,未知的欄位是沒有被拋棄的。此後,如果訊息被序列化,未知的欄位會隨之一起被序列化——所以,如果訊息傳到了新程式碼那裡,則新的欄位仍然可用。注意:對Python來說,對未知欄位的保留策略是無效的。

²  非required的欄位可以移除——只要它們的標識號在新的訊息型別中不再使用(更好的做法可能是重新命名那個欄位,例如在欄位前新增“OBSOLETE_”字首,那樣的話,使用的.proto檔案的使用者將來就不會無意中重新使用了那些不該使用的標識號)。

²  一個非required的欄位可以轉換為一個擴充套件,反之亦然——只要它的型別和標識號保持不變。

²  int32, uint32, int64, uint64,和bool是全部相容的,這意味著可以將這些型別中的一個轉換為另外一個,而不會破壞向前、向後的相容性。如果解析出來的數字與對應的型別不相符,那麼結果就像在C++中對它進行了強制型別轉換一樣(例如,如果把一個64位數字當作int32來讀取,那麼它就會被截斷為32位的數字)。

²  sint32和sint64是互相相容的,但是它們與其他整數型別不相容。

²  string和bytes是相容的——只要bytes是有效的UTF-8編碼。

²  巢狀訊息與bytes是相容的——只要bytes包含該訊息的一個編碼過的版本。

²  fixed32與sfixed32是相容的,fixed64與sfixed64是相容的。

擴充套件

通過擴充套件,可以將一個範圍內的欄位標識號宣告為可被第三方擴充套件所用。然後,其他人就可以在他們自己的.proto檔案中為該訊息型別宣告新的欄位,而不必去編輯原始檔案了。看個具體例子:

message Foo {

  // …

  extensions 100 to 199;

}

這個例子表明:在訊息Foo中,範圍[100,199]之內的欄位標識號被保留為擴充套件用。現在,其他人就可以在他們自己的.proto檔案中新增新欄位到Foo裡了,但是新增的欄位標識號要在指定的範圍內——例如:

extend Foo {

  optional int32 bar = 126;

}

這個例子表明:訊息Foo現在有一個名為bar的optional int32欄位。

當用戶的Foo訊息被編碼的時候,資料的傳輸格式與使用者在Foo裡定義新欄位的效果是完全一樣的。

然而,要在程式程式碼中訪問擴充套件欄位的方法與訪問普通的欄位稍有不同——生成的資料訪問程式碼為擴充套件準備了特殊的訪問函式來訪問它。例如,下面是如何在C++中設定bar的值:

Foo foo;
foo.SetExtension(bar, 15);

類似地,Foo類也定義了模板函式HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及AddExtension()。這些函式的語義都與對應的普通欄位的訪問函式相符。要檢視更多使用擴充套件的資訊,請參考相應語言的程式碼生成指南。注:擴充套件可以是任何欄位型別,包括訊息型別。

巢狀的擴充套件

可以在另一個型別的範圍內宣告擴充套件,如:

message Baz {

  extend Foo {

    optional int32 bar = 126;

  }

  …

}

在此例中,訪問此擴充套件的C++程式碼如下:

Foo foo;

foo.SetExtension(Baz::bar, 15);

一個通常的設計模式就是:在擴充套件的欄位型別的範圍內定義該擴充套件——例如,下面是一個Foo的擴充套件(該擴充套件是Baz型別的),其中,擴充套件被定義為了Baz的一部分:

message Baz {

  extend Foo {

    optional Baz foo_ext = 127;

  }

  …

}

然而,並沒有強制要求一個訊息型別的擴充套件一定要定義在那個訊息中。也可以這樣做:

message Baz {

  …

}

extend Foo {

  optional Baz foo_baz_ext = 127;

}

事實上,這種語法格式更能防止引起混淆。正如上面所提到的,巢狀的語法通常被錯誤地認為有子類化的關係——尤其是對那些還不熟悉擴充套件的使用者來說。

Ø  選擇可擴充套件的標符號

在同一個訊息型別中一定要確保兩個使用者不會擴充套件新增相同的標識號,否則可能會導致資料的不一致。可以通過為新專案定義一個可擴充套件標識號規則來防止該情況的發生。

如果標識號需要很大的數量時,可以將該可擴充套件標符號的範圍擴大至max,其中max是229 - 1, 或536,870,911。如下所示:

message Foo {

  extensions 1000 to max;

}

通常情況下在選擇標符號時,標識號產生的規則中應該避開[19000-19999]之間的數字,因為這些已經被Protocol Buffers實現中預留了。

包(Package)

當然可以為.proto檔案新增一個可選的package宣告符,用來防止不同的訊息型別有命名衝突。如:

package foo.bar;

message Open { ... }

在其他的訊息格式定義中可以使用包名+訊息名的方式來定義域的型別,如:

message Foo {

  ...

  required foo.bar.Open open = 1;

  ...

}

包的宣告符會根據使用語言的不同影響生成的程式碼。對於C++,產生的類會被包裝在C++的名稱空間中,如上例中的Open會被封裝在foo::bar空間中;對於Java,包宣告符會變為java的一個包,除非在.proto檔案中提供了一個明確有java_package;對於Python,這個包宣告符是被忽略的,因為Python模組是按照其在檔案系統中的位置進行組織的。

Ø  包及名稱的解析

Protocol buffer語言中型別名稱的解析與C++是一致的:首先從最內部開始查詢,依次向外進行,每個包會被看作是其父類包的內部類。當然對於(foo.bar.Baz)這樣以“.”分隔的意味著是從最外圍開始的。ProtocolBuffer編譯器會解析.proto檔案中定義的所有型別名。對於不同語言的程式碼生成器會知道如何來指向每個具體的型別,即使它們使用了不同的規則。

定義服務(Service)

如果想要將訊息型別用在RPC(遠端方法呼叫)系統中,可以在.proto檔案中定義一個RPC服務介面,protocol buffer編譯器將會根據所選擇的不同語言生成服務介面程式碼及存根。如,想要定義一個RPC服務並具有一個方法,該方法能夠接收SearchRequest並返回一個SearchResponse,此時可以在.proto檔案中進行如下定義:

service SearchService {

  rpc Search (SearchRequest) returns (SearchResponse);

}

protocol編譯器將產生一個抽象介面SearchService以及一個相應的存根實現。存根將所有的呼叫指向RpcChannel,它是一個抽象介面,必須在RPC系統中對該介面進行實現。如,可以實現RpcChannel以完成序列化訊息並通過HTTP方式來發送到一個伺服器。換句話說,產生的存根提供了一個型別安全的介面用來完成基於protocolbuffer的RPC呼叫,而不是將你限定在一個特定的RPC的實現中。C++中的程式碼如下所示:

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;
  

// The protocol compiler generates the SearchService class based on the
  // definition given above.
 

service = new SearchService::Stub(channel);
  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, request, response, protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

所有service類都必須實現Service介面,它提供了一種用來呼叫具體方法的方式,即在編譯期不需要知道方法名及它的輸入、輸出型別。在伺服器端,通過服務註冊它可以被用來實現一個RPC Server。

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

選項(Options)

在定義.proto檔案時能夠標註一系列的options。Options並不改變整個檔案宣告的含義,但卻能夠影響特定環境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到。

一些選項是檔案級別的,意味著它可以作用於最外範圍,不包含在任何訊息內部、enum或服務定義中。一些選項是訊息級別的,意味著它可以用在訊息定義的內部。當然有些選項可以作用在域、enum型別、enum值、服務型別及服務方法中。到目前為止,並沒有一種有效的選項能作用於所有的型別。

如下就是一些常用的選擇:

²  java_package (file option): 這個選項表明生成java類所在的包。如果在.proto檔案中沒有明確的宣告java_package,就採用預設的包名。當然了,預設方式產生的java包名並不是最好的方式,按照應用名稱倒序方式進行排序的。如果不需要產生java程式碼,則該選項將不起任何作用。如:

option java_package = "com.example.foo";

²  java_outer_classname (file option): 該選項表明想要生成Java類的名稱。如果在.proto檔案中沒有明確的java_outer_classname定義,生成的class名稱將會根據.proto檔案的名稱採用駝峰式的命名方式進行生成。如(foo_bar.proto生成的java類名為FooBar.java),如果不生成java程式碼,則該選項不起任何作用。如:

option java_outer_classname = "Ponycopter";

²  optimize_for (fileoption): 可以被設定為 SPEED, CODE_SIZE,or LITE_RUNTIME。這些值將通過如下的方式影響C++及java程式碼的生成:

·        SPEED (default): protocol buffer編譯器將通過在訊息型別上執行序列化、語法分析及其他通用的操作。這種程式碼是最優的。

·        CODE_SIZE: protocol buffer編譯器將會產生最少量的類,通過共享或基於反射的程式碼來實現序列化、語法分析及各種其它操作。採用該方式產生的程式碼將比SPEED要少得多,但是操作要相對慢些。當然實現的類及其對外的API與SPEED模式都是一樣的。這種方式經常用在一些包含大量的.proto檔案而且並不盲目追求速度的應用中。

·        LITE_RUNTIME: protocol buffer編譯器依賴於執行時核心類庫來生成程式碼(即採用libprotobuf-lite 替代libprotobuf)。這種核心類庫由於忽略了一些描述符及反射,要比全類庫小得多。這種模式經常在移動手機平臺應用多一些。編譯器採用該模式產生的方法實現與SPEED模式不相上下,產生的類通過實現MessageLite介面,但它僅僅是Messager介面的一個子集。

option optimize_for = CODE_SIZE;

²  cc_generic_servicesjava_generic_servicespy_generic_services (file options): 在C++、java、python中protocol buffer編譯器是否應該基於服務定義產生抽象服務程式碼。由於歷史遺留問題,該值預設是true。但是自2.3.0版本以來,它被認為通過提供程式碼生成器外掛來對RPC實現更可取,而不是依賴於“抽象”服務。

// This file relies on plugins to generate service code.

option cc_generic_services = false;

option java_generic_services = false;

option py_generic_services = false;

²  message_set_wire_format (message option):如果該值被設定為true,該訊息將使用一種不同的二進位制格式來與Google內部的MessageSet的老格式相相容。對於Google外部的使用者來說,該選項將不會被用到。如下所示:

message Foo {

  option message_set_wire_format = true;

  extensions 4 to max;

}

²  packed (field option): 如果該選項在一個整型基本型別上被設定為真,則採用更緊湊的編碼方式。當然使用該值並不會對數值造成任何損失。在2.3.0版本之前,解析器將會忽略那些非期望的包裝值。因此,它不可能在不破壞現有框架的相容性上而改變壓縮格式。在2.3.0之後,這種改變將是安全的,解析器能夠接受上述兩種格式,但是在處理protobuf老版本程式時,還是要多留意一下。

repeated int32 samples = 4 [packed=true];

²  deprecated (field option): 如果該選項被設定為true,表明該欄位已經被棄用了,在新程式碼中不建議使用。在多數語言中,這並沒有實際的含義。在java中,它將會變成一個 @Deprecated註釋。也許在將來,其它基於語言宣告的程式碼在生成時也會如此使用,當使用該欄位時,編譯器將自動報警。如:

optional int32 old_field = 6 [deprecated=true];

Ø 自定義選項

ProtocolBuffers允許自定義並使用選項。該功能應該屬於一個高階特性,對於大部分人是用不到的。由於options是定在 google/protobuf/descriptor.proto中的,因此你可以在該檔案中進行擴充套件,定義自己的選項。如:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {

  optional string my_option = 51234;

}

message MyMessage {

  option (my_option) = "Hello world!";

}

在上述程式碼中,通過對MessageOptions進行擴充套件定義了一個新的訊息級別的選項。當使用該選項時,選項的名稱需要使用()包裹起來,以表明它是一個擴充套件。在C++程式碼中可以看出my_option是以如下方式被讀取的。

string value = MyMessage::descriptor()->options().GetExtension(my_option);

在Java程式碼中的讀取方式如下:

String value = MyProtoFile.MyMessage.getDescriptor().getOptions().getExtension(MyProtoFile.myOption);

正如上面的讀取方式,定製選項對於Python並不支援。定製選項在protocol buffer語言中可用於任何結構。下面就是一些具體的例子:

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {

  optional string my_file_option = 50000;

}

extend google.protobuf.MessageOptions {

  optional int32 my_message_option = 50001;

}

extend google.protobuf.FieldOptions {

  optional float my_field_option = 50002;

}

extend google.protobuf.EnumOptions {

  optional bool my_enum_option = 50003;

}

extend google.protobuf.EnumValueOptions {

  optional uint32 my_enum_value_option = 50004;

}

extend google.protobuf.ServiceOptions {

  optional MyEnum my_service_option = 50005;

}

extend google.protobuf.MethodOptions {

  optional MyMessage my_method_option = 50006;

}

option (my_file_option) = "Hello world!";

message MyMessage {

  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];

  optional string bar = 2;

}

enum MyEnum {

  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];

  BAR = 2;

}

message RequestType {}

message ResponseType {}

service MyService {

  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {

    // Note:  my_method_option has type MyMessage.  We can set each field

    //   within it using a separate "option" line.

    option (my_method_option).foo = 567;

    option (my_method_option).bar = "Some string";

  }

}

注:如果要在該選項定義之外使用一個自定義的選項,必須要由包名 + 選項名來定義該選項。如:

// foo.proto

import "google/protobuf/descriptor.proto";

package foo;

extend google.protobuf.MessageOptions {

  optional string my_option = 51234;

}

// bar.proto

import "foo.proto";

package bar;

message MyMessage {

  option (foo.my_option) = "Hello world!";

}

最後一件事情需要注意:因為自定義選項是可擴充套件的,它必須象其它的域或擴充套件一樣來定義標識號。正如上述示例,[50000-99999]已經被佔用,該範圍內的值已經被內部所使用,當然了你可以在內部應用中隨意使用。如果你想在一些公共應用中進行自定義選項,你必須確保它是全域性唯一的。可以通過[email protected]來獲取全域性唯一標識號。

生成訪問類

可以通過定義好的.proto檔案來生成Java、Python、C++程式碼,需要基於.proto檔案執行protocol buffer編譯器protoc。執行的命令如下所示:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

·        IMPORT_PATH聲明瞭一個.proto檔案所在的具體目錄。如果忽略該值,則使用當前目錄。如果有多個目錄則可以 對--proto_path 寫多次,它們將會順序的被訪問並執行匯入。-I=IMPORT_PATH是它的簡化形式。

·        當然也可以提供一個或多個輸出路徑:

o   --cpp_out 在目標目錄DST_DIR中產生C++程式碼,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/cpp-generated.html中檢視更多。

o   --java_out 在目標目錄DST_DIR中產生Java程式碼,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/java-generated.html中檢視更多。

o   --python_out 在目標目錄 DST_DIR 中產生Python程式碼,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/python-generated.html中檢視更多。

     作為一種額外的使得,如果DST_DIR 是以.zip或.jar結尾的,編譯器將輸出結果打包成一個zip格式的歸檔檔案。.jar將會輸出一個Java JAR宣告必須的manifest檔案。注:如果該輸出歸檔檔案已經存在,它將會被重寫,編譯器並沒有做到足夠的智慧來為已經存在的歸檔檔案新增新的檔案。

·        你必須提供一個或多個.proto檔案作為輸入。多個.proto檔案能夠一次全部宣告。雖然這些檔案是相對於當前目錄來命名的,每個檔案必須在一個IMPORT_PATH中,只有如此編譯器才可以決定它的標準名稱。


相關推薦

Protobuf 協議語言指南

  l  定義一個訊息(message)型別 l  標量值型別 l  Optional 的欄位及預設值 l  列舉 l  使用其他訊息型別 l  巢狀型別 l  更新一個訊息型別 l  擴充套件 l&n

protobuf協議語言指南

Protobuf語言指南 l  定義一個訊息(message)型別 l  標量值型別 l  Optional 的欄位及預設值 l  列舉 l  使用其他訊息型別 l  巢狀型別 l  更新一個訊息型別 l  擴充套件 l  包(package) l  定義服務(servic

Protobuf語言指南(proto3)

程序是系統進行資源分配最小單元,執行緒是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位.程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體等資源。 系列文章 "python併發程式設計之threading執行緒(一)" "pyth

Protobuf語言指南

ProtoBuf開發者指南:http://gashero.yeax.com/?p=108 語言指南http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/proto.html 風格http://code.g

Protobuf語言指南——.proto檔案語法詳解

Protobuf語言指南 l  定義一個訊息(message)型別 l  標量值型別 l  Optional 的欄位及預設值 l  列舉 l  使用其他訊息型別 l  巢狀型別 l  更新一個訊息型別 l  擴充套件 l  包(package) l 

Protocol Buffers(Protobuf) 官方文件--Protobuf語言指南

約定:為方便書寫,ProtocolBuffers在下文中將已Protobuf代替。 本指南將向您描述如何使用protobuf定義i結構化Protobuf資料,包括.proto檔案語法和如何使用.proto檔案生成資料存取類。 作為一個參考指南,本文件將以示例的形式一步步向您

開源專案SMSS發開指南(三)——protobuf協議設計

本文的第一部分將介紹protobuf使用基礎以及如何利用protobuf設計通訊協議。第二部分會給出smss專案的協議設計規範和原始碼講解。 一.Protobuf使用基礎 什麼是protobuf protobuf是谷歌研發的一種資料序列化和儲存技術。主要可以用來解決網路通訊中異構系統的通訊和資料持久化,與同類

Swift4.2語言指南(十九) 錯誤處理

all 是否 state count pattern new self. 價值 nts 錯誤處理是響應程序中的錯誤條件並從中恢復的過程。Swift為在運行時拋出,捕獲,傳播和操縱可恢復的錯誤提供了一流的支持。 某些操作無法保證始終完成執行或產生有用的輸出。Optional

Swift4.2語言指南(二十二) 擴展

its 浮點 hello 兩個 責任 enum 添加 origin 計算 擴展為現有的類,結構,枚舉或協議類型添加新功能。這包括擴展您無法訪問原始源代碼的類型的能力(稱為追溯建模)。擴展類似於Objective-C中的類別。(與Objective-C類別不同,Swift擴展

Swift4.2語言指南(二十七) 訪問控制

元組 導出 簡潔 實例 嵌套類 同一文件 分布 包含 mef 訪問控制限制從其他源文件和模塊中的代碼訪問部分代碼。此功能使您可以隱藏代碼的實現細節,並指定一個首選接口,通過該接口可以訪問和使用該代碼。 您可以為各個類型(類,結構和枚舉)以及屬於這些類型的屬性,方法,初始值

Swift4.2語言指南(二十一) 嵌套類型

十一點 案例 價值 常量 clu second 指南 撲克牌 values 通常創建枚舉以支持特定類或結構的功能。類似地,定義純粹在更復雜類型的上下文中使用的實用程序類和結構可能是方便的。為此,Swift允許您定義嵌套類型,從而在它們支持的類型的定義中嵌套支持枚舉,類和結構

Swift4.2語言指南(十五) 繼承

車輛 關鍵字 讀寫 說明書 前綴 rdquo 只讀 swift 錯誤 類可以從另一個類繼承方法,屬性和其他特性。當一個類繼承自另一個類時,繼承類稱為子類,它繼承的類稱為其超類。繼承是一種基本行為,它將類與Swift中的其他類型區分開來。 Swift中的類可以調用和訪問屬於

Protobuf 協議的Java應用sample

Protobuf協議,全稱:Protocol Buffer 它跟JSON,XML一樣,是一個規定好的資料傳播格式。不過,它的序列化和反序列化的效率太變態了…… 來看看幾張圖你就知道它有多變態。 Protobuf的Java例項 一、 安裝Protobuf

protobuf協議java版本使用說明

1. 初始話協議欄位 xxx .proto syntax = "proto3"; //protobuf版本 option java_package = "com.xxx.protobuf"; //生成java程式碼的資料包路徑 option java_outer_classna

Swift語言指南

這份指南匯集了Swift語言主流學習資源,並以開發者的視角整理編排。 P.S. 考慮到本文件內專案收錄部分過於雜碎,因此,借2014年結束之季,額外整理了一份《Swift開源專案精選》,順帶短文一篇《致Swift開發者》,希望大家喜歡。 目錄

Swift 語言指南

https://github.com/ipader/SwiftGuide 這份指南匯集了 Swift 語言主流學習資源,並以開發者的視角整理編排。 想了解關於該指南及 Swift 更多資訊的同學,可以閱讀短文《致 Swift 開發者》。 想快速

Swift 語言指南( Swift 語言主流學習資源)

@Swift指南 更新於 2016-3-21,更新內容詳見 Issue 49。往期更新回顧詳見《收錄週報》   這份指南彙集了 Swift 語言主流學習資源,並以開發者的視角整理編排。對於精選專案及文章,可直接訪問《Swift 專案精選》和《Swift 文章精選》

網路通訊Socket+Protobuf協議

不多說了,直接貼程式碼 大家好,如果做即時通訊,相信大家對socket也有一定的瞭解,下面主要是對socket長連線,資料封包解包,外加protobuf的例子,希望能對各位有所幫助 import java.io.BufferedReader; import java.

OAuth2.0 協議入門指南

本文希望以應用場景的角度出發,幫助大家快隨瞭解OAuth協議流程,更為清楚明白的介紹在各種情況使用什麼授權模式更為合適。OAuth2 官網原文地址 本系列相關文章:OpenID Connect 協議入門指南SAML2.0入門指南 1. 協議中各種角色:應用、API和使用

Protobuf協議應用乾貨

  Protobuf應用廣泛,尤其作為網路通訊協議最為普遍。本文將詳細描述幾個讓人眼前一亮的protobuf協議設計,對準備應用或