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中,只有如此编译器才可以决定它的标准名称。

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中,只有如此编译器才可以决定它的标准名称。

猜你喜欢

转载自blog.csdn.net/wangrenhaioylj/article/details/109211297