首页 > 编程语言 >详解 Protobuf 在 C++ 下 Message、enum、Service 的使用

详解 Protobuf 在 C++ 下 Message、enum、Service 的使用

时间:2024-06-06 19:58:32浏览次数:13  
标签:const Protobuf Service protobuf enum NAMESPACE method ID PROTOBUF

这篇文章主要目的是介绍Protobuf的常用知识,包括前置声明,message,service,enum 等。

声明

// 使用 proto3 语法
syntax = "proto3";

// 定义一个名为 Greeter 的包
package Greeter;

// 开启生成通用服务代码的选项
option cc_generic_services = true;

syntax 用于提示 protoc 使用哪个版本的 Protobuf(proto2 || proto3),package 定义的包会在 protoc 编译之后被翻译为namespace,就如上面示例中的 Greeter 会被翻译为 namespace Greeter{};。cc_generic_services 设置为 true 用于提示 protoc 生产通用服务代码,简单来说就是 example 示例中需要继承的 RPC 服务类就需要 cc_generic_services 设置开启才能生成。

假设以下示例均被定义在 package Greeter 下

message

message 为消息类型,这是 protobuf 中最常用的类型。任意 message 都会在 protoc 编译后生成同名的类以及 message 内部数据对应的 setter 方法和 getter 方法(该方法和变量同名)。

示例如下:

// 定义一个名为 HelloRequest 的消息类型,其中包含一个名为 name 的字段
message HelloRequest {
    string name = 1;
}

在C++代码中使用它:

Greeter::HelloRequest* request;
request->set_name("noname");
std::string name = request->name();
std::cout << name << std::endl;

enum

enum Status {
    OK = 0;
    ERROR = 1;
}

enum 是 Protobuf 中的枚举类型,我们可以利用 enum 设计响应码,错误码等。protoc 编译后可以在 C++ 代码中以如下方式调用:

Greeter::Status::OK

在 message 中定义 Status 类型然后使用:

message HelloReply {
    string message = 1;
    Status status = 2;
}
Greeter::HelloReply* response;
response->set_status(Greeter::Status::OK);

service

service 是 protobuf 提供的用于定义 rpc 服务的关键字。我们可以使用 service 定义一个 rpc 服务,然后在该服务内定义多个 rpc 方法,每个 rpc 方法都接受一个用于请求的 message 类型的和用于 响应的 message 类型。

示例如下:

service GreetingService {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

在上述示例中,我们定义了一个名为 GreetingService 的 RPC 服务,然后在 GreetingService 下定义了一个名为 SayHello 的 rpc 方法,接收 HelloRequest 类型的 message, 响应 HelloReply 类型的 message。

当使用 protoc 编译 proto 文件后 service 对应 .pb.h 中的如下内容:

class GreetingService : public ::PROTOBUF_NAMESPACE_ID::Service {
 protected:
  // This class should be treated as an abstract interface.
  inline GreetingService() {};
 public:
  virtual ~GreetingService();

  typedef GreetingService_Stub Stub;

  static const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* descriptor();

  virtual void SayHello(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::Greeter::HelloRequest* request,
                       ::Greeter::HelloReply* response,
                       ::google::protobuf::Closure* done);

  // implements Service ----------------------------------------------

  const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor();
  void CallMethod(const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method,
                  ::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                  const ::PROTOBUF_NAMESPACE_ID::Message* request,
                  ::PROTOBUF_NAMESPACE_ID::Message* response,
                  ::google::protobuf::Closure* done);
  const ::PROTOBUF_NAMESPACE_ID::Message& GetRequestPrototype(
    const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;
  const ::PROTOBUF_NAMESPACE_ID::Message& GetResponsePrototype(
    const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;

 private:
  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(GreetingService);
};

也就是说 service 被 protoc 编译为对应的抽象类,抽象类下提供了通用的抽象接口。下面我们一一介绍这些抽象接口。

注意:抽象类中的 PROTOBUF_NAMESPACE_ID 就是 google::protobuf,所以我们覆盖虚函数的时候使用 goole::protobu 代替 PROTOBUF_NAMESPACE_ID。

构造函数

// ...
 protected:
  // This class should be treated as an abstract interface.
  inline GreetingService() {};
// ...

这里的构造函数访问权限是 protected,protected 的特点是外部不可见,内部可见且继承可见,所以将构造函数设置为 protected 也就意味着无法直接实例化类,但是构造函数能被派生类调用,也就意味着派生类继承后能正常完成实例化。所以这种做法常用于设计抽象基类,这里的 GreetingService 就是一个抽象基类。我们在实现 rpc 服务接口时,就会对这个抽象基类进行继承。

descriptor

// ...
    static const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* descriptor();
// ...
    const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor();

这里的 descriptor() 和 GetDescriptor() 会获取 GreetingService 服务的描述符,服务描述符包含了服务的名称、rpc方法的数量以及方法的描述符等。而方法的描述符则包含了方法的名称、方法的输入输出描述符。descriptor() 和 GetDescriptor() 的区别在于它们的调用方式不同。descriptor() 是静态方法,可以在不创建实例的情况下调用。而GetDescriptor() 是非静态方法,需要先创建类的实例再进行调用。

使用示例:

// 获取 GreetingService 服务的描述
const ::google::protobuf::ServiceDescriptor* service_desc 
    = Greeter::GreetingService::descriptor();

// 获取服务的名称
std::string service_name = service_desc->name();

// 获取服务中的方法数量
int methodCnt = service_desc->method_count();

// 遍历每个方法
for(int i = 0; i < methodCnt; i++) {
    // 获取方法的描述
    const google::protobuf::MethodDescriptor *pmethodDesc =
        service_desc->method(i);
    
    // 获取方法的名称
    std::string method_name = pmethodDesc->name();

    // 获取方法的输入描述
    const google::protobuf::Descriptor *input_type = pmethodDesc->input_type();
    // 获取方法的输入类型
    std::string input_type_name = input_type->name();

    // 获取方法的输出描述
    const google::protobuf::Descriptor *output_type = pmethodDesc->output_type();
    // 获取方法的输出类型
    std::string output_type_name = output_type->name();
}

virtual method

// ...
  virtual void SayHello(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::Greeter::HelloRequest* request,
                       ::Greeter::HelloReply* response,
                       ::google::protobuf::Closure* done);
// ...

我们在继承了 GreetingSevice 的类中通过重写这个虚函数来完成 rpc 方法的具体实现。

class GreeterServiceImpl final : public Greeter::GreetingService {
    /**
     * SayHello 是一个 RPC 方法,用于发送问候
     *
     * @param controller: 指向 RpcController 对象的指针,用于控制 RPC 的行为。
     * @param request: 指向 HelloRequest 对象的指针,其中包含 RPC 方法的请求参数。
     * @param response: 指向 HelloReply 对象的指针,应在其中设置 RPC 方法的响应结果。
     * @param done: 指向 Closure 对象的指针,表示 RPC 方法完成后应执行的操作。
     */
    void SayHello
        (
            RpcController* controller,
            const ::Greeter::HelloRequest* request,
            ::Greeter::HelloReply* response,
            Closure* done
        ) {
            // 从请求中获取名字
            std::string name = request->name();

            // 创建一条包含名字的问候消息
            std::string message = "Hello, " + name + "!";

            // 将问候消息设置为响应的消息
            response->set_message(message);

            // 将响应的状态设置为 OK
            response->set_status(Greeter::Status::OK);

            // 运行 done 的 Run 方法,表示 RPC 方法已经完成
            done->Run();
        }
};

CallMethod

// ...
  /**
   * @brief 调用服务的方法,传入请求消息和响应消息,以及回调函数
   * 
   * @param method 方法描述符,描述了要调用的方法
   * @param controller 控制器,用于控制 RPC 调用
   * @param request 请求消息,包含了调用方法所需的参数
   * @param response 响应消息,用于存储方法的返回值
   * @param done 闭包,当 RPC 方法完成时会被调用
   */
  void CallMethod(
      const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method,
      ::PROTOBUF_NAMESPACE_ID::RpcController* controller,
      const ::PROTOBUF_NAMESPACE_ID::Message* request,
      ::PROTOBUF_NAMESPACE_ID::Message* response,
      ::google::protobuf::Closure* done
  );
// ...

CallMethod 是 RPC 服务调用的关键方法,CallMethod 会调用对应 Rpc 请求的 mehod,传递请求消息作为函数参数,并将结果封装为响应消息类型,并且调用在方法执行完毕后调用,done 定义的回调函数。

使用示例:

// 从服务对象和方法描述符中获取请求消息的原型,并创建一个新的请求消息
google::protobuf::Message *request =
    service->GetRequestPrototype(method).New();

// 从服务对象和方法描述符中获取响应消息的原型,并创建一个新的响应消息
google::protobuf::Message *response =
    service->GetResponsePrototype(method).New();

// 只需要知道上述内容是请求消息类型和响应消息类型即可,通过method方法描述符获取消息原型的示例在最下面的部分。

// 创建一个回调函数,当服务的方法调用完成后,这个回调函数会被调用
// 这个回调函数会调用 RpcProvider 的 SendRpcResponse 方法,将响应消息发送给客户端
// RpcProvider 是当前对象, TcpConnectionPtr 和 Message是需要接受的参数类型
/**
 * @param this: 指向当前对象的指针,即 RpcProvider 对象。
 * @param &RpcProvider::SendRpcResponse: 成员函数指针,指向 RpcProvider 类的 SendRpcResponse 成员函数。
 * @param conn: 一个指向 TcpConnection 的智能指针,表示一个 TCP 连接。这个参数将被传递给 SendRpcResponse 函数。
 * @param response: 指向 google::protobuf::Message 的指针,表示一个 Protobuf 消息。这个参数将被传递给 SendRpcResponse 函数。
 */
google::protobuf::Closure *done =
    google::protobuf::NewCallback<RpcProvider,
                                    const muduo::net::TcpConnectionPtr &,
                                      google::protobuf::Message *>(
          this, &RpcProvider::SendRpcResponse, conn, response);

// 调用服务的方法,传入请求消息和响应消息,以及回调函数
service->CallMethod(method, nullptr, request, response, done);
// RpcProvider::SendRpcResponse 函数定义
void RpcProvider::SendRpcResponse(const muduo::net::TcpConnectionPtr &conn,
                                  google::protobuf::Message *response) {
// 具体实现
}

其他

// ...
  const ::PROTOBUF_NAMESPACE_ID::Message& GetRequestPrototype(
    const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;
  const ::PROTOBUF_NAMESPACE_ID::Message& GetResponsePrototype(
    const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;

 private:
  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(GreetingService);
// ...

GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(GreetingService) 是 protobuf 中定义的一个宏,用于删除复制构造函数和复制赋值运算符。

GetRequestPrototype 和 GetResponsePrototype的使用方式就是通过方法描述符来获取一个请求消息原型或者响应消息圆型,如下

//...
// 使用上述遍历方法获取其中的方法描述符
// 该method
const google::protobuf::MethodDescriptor *pmethodDesc =
    service_desc->method(i);

// 获取请求消息原型
google::protobuf::Message *request =
    service->GetRequestPrototype(method).New();

// 获取响应消息原型
google::protobuf::Message *response =
    service->GetResponsePrototype(method).New();

在 RpcChannel 中也存在 CallMethod 这个 CallMethod 是提供给调用方使用的,虚函数声明如下:

  virtual void CallMethod(const MethodDescriptor* method,
                          RpcController* controller, const Message* request,
                          Message* response, Closure* done) = 0;

不同于被调用方的 CallMethd 这个 CallMthod 请求和响应对象不需要是任何特定的类,只要它们的描述符是 method->input_type() 和 method->output_type() 即可。

标签:const,Protobuf,Service,protobuf,enum,NAMESPACE,method,ID,PROTOBUF
From: https://blog.csdn.net/a2025834646/article/details/139413130

相关文章

  • 【VMware vSphere】安装配置Update Manager Download Service(UMDS)作为 vLCM 的下载存
    VMwarevSphereUpdateManagerDownloadService(UMDS)是vSphereLifecycleManager(vLCM)的可选模块。我在之前文章中提到这个功能,当vSphere环境能够连接Internet时,我们可以使用vLCM的在线Internet下载源获取修补程序,当vSphere环境不能连接Internet时,您可以在您的......
  • 使用 jar 方式,快速运行 minecraft-service
    在Linux系统中,要将命令封装成.service文件,需要创建一个systemd服务单元文件。下面是创建一个名为minecraft.service​的示例,它会运行你提供的java​命令来启动MinecraftSpigot服务器。打开终端。使用文本编辑器创建一个新的.service文件。你可以使用nano​或者vi​。例如:s......
  • XML-RPC实现WebService简单PHP程序示例 及 Closure闭包中的bind与bindTo方法的区别
    一、XML-RPC实现WebService简单PHP程序示例    WebService就是为了异构系统的通信而产生的,它基本的思想就是使用基于XML的HTTP的远程调用提供一种标准的机制,而省去建立一种新协议的需求。目前进行WebService通信有两种协议标准,一种是XML-RPC,另外一种是SOAP。XML-RPC比较......
  • HttpContext探究之RequestServices
    HttpContext探究之RequestServices在一篇随笔中提到了中间件的构造方式,主要有两种,第一种是直接从容器里面获取,第二种是构造函数的参数从容器里面获取,这两者都离不开容器,也就是serviceprovide,而RequestService则是里面重要的内容RequestServices是什么HttpContext.RequestServi......
  • enumerate()函数的用法与实例
    enumerate()函数是Python中常用的内置函数之一,用于同时遍历集合对象(如列表、元组、字符串等)的索引和元素。用法:enumerate()函数接受一个可迭代对象作为参数,并返回一个生成器对象,每次迭代生成器时,都会返回一个由索引和对应元素值组成的元组。语法:enumerate(iterable,start......
  • PEnum_DistributionSystemElectricalCategory
    PEnum_DistributionSystemElectricalCategory  TypevaluesTypeDescriptionEXTRALOWVOLTAGENodescriptionavailable.HIGHVOLTAGENodescriptionavailable.LOWVOLTAGENodescriptionavailable.OTHERrequiredcategorynotonscaleNOTKN......
  • C# :IQueryable & IEnumerable
    1.IEnumerablenamespaceSystem.Collections:publicinterfaceIEnumerable{publicIEnumeratorGetEnumerator();}publicinterfaceIEnumerator{pubilcobjectCurrent{get;}publicboolMoveNext();publicvoidReset();}IEnumerable......
  • enum4linux一键查询SMB信息(KALI工具系列十六)
    目录1、KALILINUX简介 2、enum4linux工具简介 3、在KALI中使用enum4linux3.1目标主机IP(win)​编辑3.2KALI的IP  4、操作示例4.1运行工具 4.2列出用户名4.3提取用户名4.4使用自定义RID范围4.5列出组4.6列出共享文件夹4.7获取操作系统信息5、总结......
  • Android Binder 机制之 ServiceManager 模块
    ServiceManager启动源码分析以Android9.0代码为例介绍Init拉起ServiceManager进程init进程通过init.rc脚本拉起Native层的ServiceManager进程init.rc//system/core/rootdir/init.rconlate-init...triggerpost-fs#late_init事件触发pos......
  • .NET Core IServiceCollection注入 拓展方法
    一般注入比较麻烦,可以不依赖第三方组件命名空间注入usingMicrosoft.Extensions.DependencyInjection;usingNewtonsoft.Json;usingSystem.Reflection;publicstaticclassServiceCollectionExtensions{publicstaticIServiceCollectionAddTransientFromNames......