google protobuf 定義服務(service)

l  定義服務(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;
}

l  選項(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]來獲取全局唯一標識號。

l  生成訪問類

可以通過定義好的.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中,只有如此編譯器纔可以決定它的標準名稱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章