C++下Protobuf学习

C++下Protobuf简单学习

Protobuf(Protocol Buffers)协议是一种由 Google 开发的高效的跨语言的平台无关数据序列化协议,提供二进制序列化格式和相关的技术,它用于高效地序列化和反序列化结构化数据,通常用于网络通信、数据存储等场景。

Protobuf 在许多领域都得到了广泛应用,特别是在分布式系统、RPC(Remote Procedure Call)框架和数据存储中,它提供了一种高效、简洁和可扩展的方式来序列化和交换数据,Protobuf 的主要优点包括:

  • 高效性:Protobuf 序列化后的二进制数据通常比其他序列化格式(比如超级常用的JSON)更小,并且序列化和反序列化的速度更快,这对于性能敏感的应用非常有益。
  • 简洁性:Protobuf 使用一种定义消息格式的语法,它允许定义字段类型、顺序和规则(消息结构更加清晰和简洁)
  • 版本兼容性:Protobuf 支持向前和向后兼容的版本控制,使得在消息格式发生变化时可以更容易地处理不同版本的通信。
  • 语言无关性:Protobuf 定义的消息格式可以在多种编程语言中使用,这有助于跨语言的通信和数据交换(截至本文发布目前官方支持的有C++/C#/Dart/Go/Java/Kotlin/python)
  • 自动生成代码:Protobuf 通常与相应的工具一起使用,可以自动生成代码,包括序列化/反序列化代码和相关的类(减少了手动编写代码的工作量,提高效率)

消息定义

Protocol Buffer 消息message和服务service由程序员编写的 .proto 文件描述。下面显示了一个示例 消息

syntax = "proto2";		// 指定正在使用proto2语法

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}

然后执行下列命令进行编译:

protoc --cpp_out=. person.proto

protoc 编译器对 .proto 文件进行处理,会生成两个文件:person.pb.hperson.pb.cc,以操作相应的 protocol buffer。其中.proto文件中的每一个消息有一个对应的类

.proto文件中的类型和各个语言中的类型匹配:

在这里插入图片描述

服务定义

如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer 编译器将会根据所选择的不同语言生成服务接口代码及存根。

例如,想要定义一个RPC服务并具有一个Search方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:

syntax = "proto3";

message SearchRequest {
  string query = 1;
}

message SearchResponse {
  string result = 1;
}

service SearchService {
    //rpc 服务的函数名 (传入参数)返回(返回参数)
    rpc Search (SearchRequest) returns (SearchResponse);
}

对该.proto文件编译,会得到一个.pb.h和一个.pb.cc文件,包含SearchServiceSearchService_stub

SearchService类 —— 所定义的一个服务类

SearchService类继承于google::protobuf::Service类,是一个服务器端实现RPC服务的类****(因此这个过程就是程序员在.proto中定义了服务、服务方法以及服务方法需要传入的参数、返回的参数,然后通过解析将其转换成具体的语言代码形成一个服务类,类中定义了方法和调用这个方法的接口),源码大致如下:

class SearchSerive_Stub;

class SearchService : public ::google::protobuf::Service {
 public:
  SearchService();
  virtual ~SearchService();

  SearchService(const SearchService&) = delete;
  SearchService& operator=(const SearchService&) = delete;

  typedef SearchService_Stub Stub;

  static const ::google::protobuf::ServiceDescriptor* descriptor();
  virtual const ::google::protobuf::ServiceDescriptor* GetDescriptor() const;

  virtual void Search(::google::protobuf::RpcController* controller,
                      const ::search::SearchRequest* request,
                      ::search::SearchResponse* response,
                      ::google::protobuf::Closure* done);

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

  const ::google::protobuf::Message& GetRequestPrototype(
      const ::google::protobuf::MethodDescriptor* method) const override;
  const ::google::protobuf::Message& GetResponsePrototype(
      const ::google::protobuf::MethodDescriptor* method) const override;

  void CallMethod(const ::google::protobuf::MethodDescriptor* method,
                  ::google::protobuf::RpcController* controller,
                  const ::google::protobuf::Message* request,
                  ::google::protobuf::Message* response,
                  ::google::protobuf::Closure* done) override;

  const ::google::protobuf::ServiceDescriptor* service_descriptor() const override;
  void Shutdown() override;

 private:
  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SearchService);
};

下面是对这个类的详细说明:

  • 内部类别名Stub:利用typedef定义了一个SearchService_Stub类的别名stub,用于指向服务的客户端存根(Stub)。
  • 静态方法
    • descriptor():返回当前服务的描述符。
    • GetDescriptor():返回当前服务的描述符。
  • 虚方法声明
    • Search()虚函数:声明该方法接受一个 SearchRequest 请求,处理后将结果存入 SearchResponse 中,并在完成时调用 done 闭包。具体实现由派生类定义,此时生成的这个类主要用来作为基类。
  • Service 接口实现
    • GetRequestPrototype()GetResponsePrototype():根据给定的方法描述符,返回请求和响应消息的原型。
    • CallMethod():根据参数输入中的方法描述符调用本服务中相应的 RPC 方法。
    • service_descriptor():返回服务的描述符。
    • Shutdown():实现服务关闭时的逻辑。

SearchService_stub类 —— 客户端存根

SearchService_stub 类继承自SearchService类,客户端使用的存根(Stub),用于通过网络调用远程服务器上定义的 RPC 方法。

class SearchService_stub : public SearchService {
 public:
  SearchService_stub(::google::protobuf::RpcChannel* channel);
  SearchService_stub(::google::protobuf::RpcChannel* channel,
                   ::google::protobuf::Service::ChannelOwnership ownership);
  ~SearchService_stub();

  inline ::google::protobuf::RpcChannel* channel() { return channel_; }

  // implements kvServerRpc ------------------------------------------

  void Search(::google::protobuf::RpcController* controller,
                      const ::search::SearchRequest* request,
                      ::search::SearchResponse* response,
                      ::google::protobuf::Closure* done);
 private:
  ::google::protobuf::RpcChannel* channel_;
  bool owns_channel_;
  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(kvServerRpc_Stub);
};

显然可以看出,SearchService_stub 在构造的时候主要需要传入一个google::protobuf::RpcChannel类型的指针。RpcChannel类负责管理客户端与服务器之间的网络通信,抽象了底层的网络细节,包括连接建立、数据传输、错误处理等。通过 RpcChannel,客户端可以与服务器建立连接并保持通信状态。

于是,客户端可以实例化 SearchService_stub 类,是存根可以通过传入的 RpcChannel对象与所连接的服务器进行通信。然后客户使用存根对象调用服务器上定义的 RPC 方法,例如 Search 方法。

// 示例:准备请求和响应对象
search::SearchRequest request;
search::SearchResponse response;

// 设置请求内容,例如填充 request 对象
...

// 发起远程调用
stub.Search(nullptr, &request, &response, nullptr);

CallerMethod在其中发挥的作用?

这里涉及到了两个类型的CallerMethod方法:RpcChannel::CallerMethodService::CallerMethod.

在实际使用中,当客户端通过存根对象调用远程服务器上的 RPC 方法时,最终会执行到 CallMethod 方法。继续以上面的例子,方法内部**,它会通过 **RpcChannel 对象 CallMethod 方法**来执行实际的 RPC 调用过程。

void SearchService_stub::Search(::google::protobuf::RpcController* controller,
                              const ::FooRequest* request,
                              ::FooResponse* response,
                              ::google::protobuf::Closure* done) {
  channel_->CallMethod(descriptor()->method(0),
                       controller, request, response, done);
}

具体来说,这个方法调用之后会经过以下几件事情:

  1. 创建 RpcController 对象:如果传入的 controller 参数为 nullptr,则会创建一个默认的 RpcController 对象。
  2. 调用 CallMethod 方法SearchService::Stub 类内部的 Search 方法会调用 RpcChannel 对象的 CallMethod 方法,同时传递以下参数:
    • method 参数:指定要调用的 RPC 方法的描述符。
    • controller 参数:负责控制 RPC 调用过程,例如处理超时、错误处理等。
    • request 参数:包含了客户端发送的请求消息。
    • response 参数:用于存储服务器端处理后的响应消息。
    • done 参数:在 RPC 调用完成时调用的回调函数对象。
  3. 执行 RPC 调用(在RpcChannel::CallerMethod中定义):在 RpcChannel 对象的 CallMethod方法内部,根据 method 参数确定要调用的具体 RPC 服务名称和方法名称(例如 Search),然后构建请求头,和对应的请求消息一起发送给服务器。
  4. 处理响应(在服务端中定义):服务器端接收到消息后,根据请求头中的服务名称和方法名称,调用服务对象的CallMethod方法来处理请求,并且将生成的响应消息存储在响应消息response 参数中,并在完成时调用 done 对象通知调用方。

参考文献

上述内容多摘抄自以下博客,如有侵权,请联系删除:

保姆级】Protobuf详解及入门指南-CSDN博客

概览 | 协议缓冲区文档 - ProtoBuf 中文

Protobuf 完整解析 - 公司最常用的数据交互协议 - hongxinerke - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777604.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WordPress网站违法关键词字过滤插件下载text-filter

插件下载地址:https://www.wpadmin.cn/2025.html 插件介绍 WordPress网站违法关键词字过滤插件text-filter由本站原创开发,支持中英文关键字自动替换成**号,可以通过自定义保存修改按钮增加“预设关键字”,也可以导入定义好的txt文本形式的关…

single_test_funi.py: error: the following arguments are required: img

parser.add_argument(img, defaultS/1.jpg, helpImage file) 当你已经指定了文件路径,还是报错怎么办? parser.add_argument(img, nargs?, defaultS/1.jpg, helpImage file) nargs? 表示 config 参数是可选的。如果用户没有提供这个参数&#xff0c…

【ARMv8/v9 GIC 系列 5.6 -- GIC 超优先级中断详细介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 Interrupt superpriority超优先级中断的特性和应用Physical interface interrupt signalsPhysical Group 1 Non-NMI for Current Security StatePhysical Group 1 for Other Security State, or a Group 0 Non-NMIPhysical Group 1 …

JVM原理(十八):JVM虚拟机的编译器优化技术

1. 编译器优化技术 编译器的目标虽然是做程序代码翻译为本地机器 码的工作,但其实难点并不在于能不能成功翻译出机器码,输出代码优化质量的高低才是决定编译器优秀与否的关键。 1.1. 优化技术概览 即时编译器对这些代码优化变换是建立在代码的中间表示…

基于Android Studio点餐项目,点餐app

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 实现登录、注册、注销功能,退出登录等功能, 以及基本的选择店铺点餐,加入购物车和结算等功能,以及可以增加或者减少商品的个数, 同时可以同步价格的总量。以…

两年经验前端带你重学前端框架必会的ajax+node.js+webpack+git等技术的个人学习心得、作业及bug记录 Day1

黑马程序员前端AJAX入门到实战全套教程,包含学前端框架必会的(ajaxnode.jswebpackgit),一套全覆盖 Day1 你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​…

沙龙回顾|MongoDB如何充当企业开发加速器?

数据不仅是企业发展转型的驱动力,也是开发者最棘手的问题。前日,MongoDB携手阿里云、NineData在杭州成功举办了“数据驱动,敏捷前行——MongoDB企业开发加速器”技术沙龙。此次活动吸引了来自各行各业的专业人员,共同探讨MongoDB的…

大话C语言:第27篇 内存模型

1 存储硬件概述 现代计算机遵循冯诺依曼体系结果,存储分为: 外部存储器:长期存放数据,掉电不丢失数据。例如,硬盘、flash、rom、u 盘、光盘、磁带。 内部存储器:暂时存放数据,掉电数据丢失。例…

小白学python(第六天)循环之异变

本篇文章给大家讲解的是循环语句,那么闲话少叙,我们进入正题 在c、Java中循环都是三剑客,那么大家可还记得是哪三位剑客吗 剑客一:while循环 剑客二:for循环 剑客三:do{……}while(&…

springcloud-alibba之FeignClient

代码地址&#xff1a;springcloud系列: springcloud 组件分析拆解 1.FeignClient的集成 springboot版本&#xff1a;3.1.5 springcloud组件版本&#xff1a;2022.0.4 nacos客户端的版本&#xff1a;2.3.2 1.引pom 这里引入了nacos和feginclient的版本 <dependency>…

MongoDB 单节点升级为副本集高可用集群(1主1从1仲裁)

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG、Mongodb数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享…

SpringBoot 实现视频分段播放(通过进度条来加载视频)

需求&#xff1a;现在我本地电脑中有一个文件夹&#xff0c;文件夹中都是视频&#xff0c;需要实现视频播放的功能。 问题&#xff1a;如果通过类似 SpringBoot static 文件夹的方式来实现&#xff0c;客户端要下载好完整的视频之后才可以播放&#xff0c;并且服务端也会占用大…

计算机网络之以太网

上文内容&#xff1a;总线局域网以及冲突的解决方法 1.以太网的起源 1.1起源 60年代末期&#xff0c;夏威夷大学Norman Abramson等研制ALOHA无线网络系统,实现Oahu岛上的主机和其它岛及船上的读卡机和终端通信&#xff1b; 出境信道地址&#xff1a;主机到终端&#xff1…

vue3 + 百度地图 实现多坐标生成轨迹的两种种方式

本次依然是关于百度地图中常见的一个问题&#xff0c;此次共使用了两种方式并做了一些分析及处理&#xff0c;希望有所帮助。如有问题可以评论或私信。 一、便捷方式 优点&#xff1a;便捷&#xff0c;所用的api方法是根据坐标进行计算后绘制路线&#xff0c;所以路线相对准确…

制定事件响应计划的四个关键步骤,如何做到风险闭环

一个有效的安全事件响应策略的关键组成部分有哪些&#xff1f;一个有效的安全事件响应策略包括四个关键组成部分&#xff0c;它们协同工作以确保对网络安全问题的快速和有效响应。 一个有效的安全事件响应策略的关键组成部分有哪些&#xff1f; 一个有效的安全事件响应策略包括…

Java常用算法集合扩容机制分析

基础篇 基础篇要点&#xff1a;算法、数据结构、基础设计模式 1. 二分查找 要求 能够用自己语言描述二分查找算法能够手写二分查找代码能够解答一些变化后的考法 算法描述 前提&#xff1a;有已排序数组 A&#xff08;假设已经做好&#xff09; 定义左边界 L、右边界 R&…

SQLite 命令行客户端 + Windows 批处理应用

SQLite 命令行客户端 Windows 批处理应用 下载 SQLite 客户端1. Bat 辅助脚本1. 执行SQL.bat执行 2. 导出Excel.bat执行效果 3. 导出HTML.bat执行效果 4. 清空-订单表.bat 2. 测试 SQL1. 创建订单表.sql2. 插入订单表.sql3. 查询订单表.sql4. 清空订单表.sql5. 删除订单表.sql…

linux驱动编程 - kfifo先进先出队列

简介&#xff1a; kfifo是Linux Kernel里面的一个 FIFO&#xff08;先进先出&#xff09;数据结构&#xff0c;它采用环形循环队列的数据结构来实现&#xff0c;提供一个无边界的字节流服务&#xff0c;并且使用并行无锁编程技术&#xff0c;即当它用于只有一个入队线程和一个出…

黑马的ES课程中的不足

在我自己做项目使用ES的时候&#xff0c;发现了黑马没教的方法&#xff0c;以及一些它项目的小问题 搜索时的匹配方法 这个boolQuery().should 我的项目是通过文章的标题title和内容content来进行搜索 但是黑马它的项目只用了must 如果我们的title和content都用must&#x…