protobuf協議語言指南

jopen 12年前發布 | 32K 次閱讀

Protobuf語言指南

定義一個消息(message)類型

標量值類型

Optional 的字段及默認值

枚舉

使用其他消息類型

嵌套類型

更新一個消息類型

擴展

包(package)

定義服務(service)

選項(option)

生成訪問類

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

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

定義一個消息類型

先來看一個非常簡單的例子。假設你想定義一個“搜索請求”的消息格式,每一個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以采用如下的方式來定義消息類型的.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文件中使用這些預留標識號,編譯時就會報警。

指定字段規則

所指定的消息字段修飾符必須是如下之一:

2  required:一個格式良好的消息一定要含有1個這種字段。表示該值是必須要設置的;

2  optional:消息格式中該字段可以有0個或1個值(不超過1個)。

2  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文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。

2  對C++來說,編譯器會為每個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每一個消息有一個對應的類。

2  對Java來說,編譯器為每一個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來創建消息類接口的)。

2  對Python來說,有點不太一樣——Python編譯器為.proto文件中的每個消息類型生成一個含有靜態描述符的模塊,,該模塊與一個元類(metaclass)在運行時(runtime)被用來創建所需的Python數據訪問類。

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

標量數值類型

一個標量消息字段可以含有一個如下的類型——該表格展示了定義于.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

可能包含任意順序的字節數據。

你可以在文章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中的一個。 其實可以很容易地實現這一點:通過向消息定義中添加一個枚舉(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例子是完全等價的。

更新一個消息類型

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

2  不要更改任何已有的字段的數值標識。

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

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

2  一個非required的字段可以轉換為一個擴展,反之亦然——只要它的類型和標識號保持不變。

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

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

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

2  嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本。

2  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";

2  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]已經被占 用,該范圍內的值已經被內部所使用,當然了你可以在內部應用中隨意使用。如果你想在一些公共應用中進行自定義選項,你必須確保它是全局唯一的。可以通過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中,只有如此編譯器才可以決定它的標準名稱。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!