Protobuf語言指南


本指南描寫敘述了怎樣使用protocolbuffer語言來構造你的protocol buffer數據,包括.proto文件語法以及怎樣生成.proto文件的數據訪問類。

本文是一個參考指南——假設要查看怎樣使用本文中描寫敘述的多個特性的循序漸進的樣例,請在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找須要的語言的教程。

定義一個消息類型

先來看一個非常easy的樣例。

假設你想定義一個“搜索請求”的消息格式。每一個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數。以及每一頁多少條查詢結果。能夠採用例如以下的方式來定義消息類型的.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數據訪問類。

你能夠從例如以下的文檔鏈接中獲取每種語言很多其它API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

標量數值類型

一個標量消息字段能夠含有一個例如以下的類型——該表格展示了定義於.proto文件里的類型,以及與之相應的、在自己主動生成的訪問類中定義的類型:

這里寫圖片描寫敘述
這里寫圖片描寫敘述

可能包括隨意順序的字節數據。

你能夠在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到很多其它“序列化消息時各種類型怎樣編碼”的信息。

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中的一個。 其實能夠非常easy地實現這一點:通過向消息定義中加入一個枚舉(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樣例是全然等價的。

更新一個消息類型

假設一個已有的消息格式已無法滿足新的需求——如,要在消息中加入一個額外的字段——可是同一時候舊版本號寫的代碼仍然可用。

不用操心!

更新消息而不破壞已有代碼是非常easy的。在更新時僅僅要記住以下的規則就可以。

  1. 不要更改不論什么已有的字段的數值標識。

  2. 所加入的不論什么字段都必須是optional或repeated的。這就意味着不論什么使用“舊”的消息格式的代碼序列化的消息能夠被新的代碼所解析。由於它們 不會丟掉不論什么required的元素。

    應該為這些元素設置合理的默認值,這樣新的代碼就能夠正確地與老代碼生成的消息交互了。相似地,新的代碼創建的消息 也能被老的代碼解析:老的二進制程序在解析的時候僅僅是簡單地將新字段忽略。

    然而,未知的字段是沒有被拋棄的。

    此后,假設消息被序列化。未知的字段會隨之中的一個 起被序列化——所以,假設消息傳到了新代碼那里,則新的字段仍然可用。注意:對Python來說,對未知字段的保留策略是無效的。

  3. 非required的字段能夠移除——僅僅要它們的標識號在新的消息類型中不再使用(更好的做法可能是重命名那個字段,比如在字段前加入“OBSOLETE_”前綴,那樣的話,使用的.proto文件的用戶將來就不會無意中又一次使用了那些不該使用的標識號)。

  4. 一個非required的字段能夠轉換為一個擴展,反之亦然——僅僅要它的類型和標識號保持不變。

  5. int32, uint32, int64, uint64,和bool是全部兼容的,這意味着能夠將這些類型中的一個轉換為另外一個。而不會破壞向前、 向后的兼容性。假設解析出來的數字與相應的類型不相符。那么結果就像在C++中對它進行了強制類型轉換一樣(比如。假設把一個64位數字當作int32來 讀取,那么它就會被截斷為32位的數字)。

  6. sint32和sint64是互相兼容的,可是它們與其它整數類型不兼容。

  7. string和bytes是兼容的——僅僅要bytes是有效的UTF-8編碼。

  8. 嵌套消息與bytes是兼容的——僅僅要bytes包括該消息的一個編碼過的版本號。

  9. 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值、服務類型及服務方法中。

到眼下為止。並沒有一種有效的選項能作用於全部的類型。

例如以下就是一些經常使用的選擇:

  1. java_package (file option): 這個選項表明生成java類所在的包。

    假設在.proto文件里沒有明白的聲明java_package,就採用默認的包名。當然了。默認方式產生的 java包名並非最好的方式。依照顧用名稱倒序方式進行排序的。假設不須要產生java代碼。則該選項將不起不論什么作用。如:

option java_package = "com.example.foo";
  1. java_outer_classname (file option): 該選項表明想要生成Java類的名稱。

    假設在.proto文件里沒有明白的java_outer_classname定義,生成的class名稱將會依據.proto文件的名稱採用駝峰式的命名方式進行生成。如(foo_bar.proto生成的java類名為FooBar.java),假設不生成java代碼,則該選項不起不論什么作用。如:

option java_outer_classname = "Ponycopter";
  1. 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_services, java_generic_services, py_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]已經被占 用,該范圍內的值已經被內部所使用,當然了你能夠在內部應用中隨意使用。

假設你想在一些公共應用中進行自己定義選項,你必須確保它是全局唯一的。能夠通過protobuf-global-extension-registry@google.com來獲取全局唯一標識號。

生成訪問類

能夠通過定義好的.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中。僅僅有如此編譯器才干夠決定它的標准名稱。

from:http://www.open-open.com/home/space.php?uid=37924&do=blog&id=5873

=========================================================

ProtoBuf開發人員指南:http://gashero.yeax.com/?p=108

官方:http://code.google.com/p/protobuf/

語言指南
http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/proto.html

風格
http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/style.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM