深入掌握 Protobuf 与 RPC 的高效结合:实现C++工程中的高效通信

news2024/9/30 15:47:15

目录

      • 一、Protobuf与RPC框架的通信流程概述
      • 二、Protobuf与RPC在C++中的实际应用
        • 2.1 定义 `.proto` 文件
        • 2.2 编译 `.proto` 文件生成C++代码
        • 2.3 实现服务器端逻辑
        • 2.4 实现客户端逻辑
        • 2.5 使用CMake构建工程
        • 2.6 编译与运行
        • 2.7 关键组件解析
        • 2.8 序列化与反序列化的实现
      • 三、关键实现与解析
      • 四、工程实践中的最佳实践
      • 五、总结
      • 附录:常用Protobuf与gRPC命令
      • 参考资料

在现代分布式系统中,高效的通信至关重要,而Protobuf(Protocol Buffers)不仅能提供数据序列化的高效性,还常被用于与RPC(远程过程调用)框架结合,优化客户端与服务器之间的通信效率。本文将详细解析如何在C++工程中使用Protobuf,并结合RPC框架,构建高性能的分布式系统通信机制。

一、Protobuf与RPC框架的通信流程概述

在这里插入图片描述

为了便于理解,以下结合Protobuf和RPC的通信流程进行详细解析:

  1. 定义IDL(接口定义语言)文件:通过Protobuf的.proto文件,定义服务接口和消息格式,这是所有客户端与服务器通信的基础。

  2. 编译IDL文件:使用Protobuf编译器(protoc),生成客户端与服务器的代码骨架。这一步使得开发者无需关心底层通信细节,大幅减少编码工作量。

  3. 客户端调用服务:客户端通过调用生成的骨架代码发起请求,Protobuf负责将消息进行序列化,并通过RPC框架的协议栈传输至服务器。

  4. 服务器处理请求:服务器接收客户端请求,反序列化数据,执行相应的业务逻辑,并将结果序列化为响应数据返回给客户端。

  5. 客户端接收响应:客户端接收到服务器的响应后,反序列化数据并继续处理后续业务逻辑。

二、Protobuf与RPC在C++中的实际应用

2.1 定义 .proto 文件

首先,我们需要通过Protobuf定义服务接口和消息结构。例如,下面的.proto文件定义了一个简单的用户服务接口,包括获取用户信息的服务:

syntax = "proto3";

package example;

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

message GetUserRequest {
    int32 id = 1;
}

message GetUserResponse {
    User user = 1;
}

service UserService {
    rpc GetUser (GetUserRequest) returns (GetUserResponse);
}
2.2 编译 .proto 文件生成C++代码

使用protoc编译器生成C++代码:

protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` user_service.proto

编译后生成的文件包含以下两部分:

  • 消息类user_service.pb.huser_service.pb.cc
  • 服务接口及骨架代码user_service.grpc.pb.huser_service.grpc.pb.cc
2.3 实现服务器端逻辑

服务器端需要实现生成的服务接口,并通过RPC服务器处理客户端的请求。下面是基于gRPC的C++服务器实现:

#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "user_service.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using example::UserService;
using example::GetUserRequest;
using example::GetUserResponse;
using example::User;

class UserServiceImpl final : public UserService::Service {
public:
    Status GetUser(ServerContext* context, const GetUserRequest* request,
                  GetUserResponse* reply) override {
        // 模拟数据库操作
        User* user = reply->mutable_user();
        user->set_id(request->id());
        user->set_name("Alice");
        user->set_email("alice@example.com");
        return Status::OK;
    }
};

void RunServer() {
    std::string server_address("0.0.0.0:50051");
    UserServiceImpl service;

    ServerBuilder builder;
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);

    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on " << server_address << std::endl;
    server->Wait();
}

int main() {
    RunServer();
    return 0;
}
2.4 实现客户端逻辑

客户端通过调用生成的代码与服务器进行通信。以下是gRPC客户端的实现代码:

#include <iostream>
#include <memory>
#include <grpcpp/grpcpp.h>
#include "user_service.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using example::UserService;
using example::GetUserRequest;
using example::GetUserResponse;

class UserServiceClient {
public:
    UserServiceClient(std::shared_ptr<Channel> channel)
        : stub_(UserService::NewStub(channel)) {}

    GetUserResponse GetUser(int id) {
        GetUserRequest request;
        request.set_id(id);

        GetUserResponse response;
        ClientContext context;

        Status status = stub_->GetUser(&context, request, &response);
        if (!status.ok()) {
            std::cerr << "RPC failed: " << status.error_message() << std::endl;
        }
        return response;
    }

private:
    std::unique_ptr<UserService::Stub> stub_;
};

int main() {
    UserServiceClient client(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
    int user_id = 1;
    GetUserResponse response = client.GetUser(user_id);
    std::cout << "User ID: " << response.user().id() << std::endl;
    std::cout << "Name: " << response.user().name() << std::endl;
    std::cout << "Email: " << response.user().email() << std::endl;
    return 0;
}
2.5 使用CMake构建工程

使用CMake构建Protobuf和gRPC项目,确保将生成的代码自动编译到项目中:

cmake_minimum_required(VERSION 3.14)
project(ProtobufRPCExample)

find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)

set(CMAKE_CXX_STANDARD 14)

protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS user_service.proto)
grpc_generate_cpp(GRPC_SRCS GRPC_HDRS user_service.proto)

add_executable(server server.cpp ${PROTO_SRCS} ${PROTO_HDRS} ${GRPC_SRCS} ${GRPC_HDRS})
target_link_libraries(server PRIVATE protobuf::libprotobuf gRPC::grpc++)

add_executable(client client.cpp ${PROTO_SRCS} ${PROTO_HDRS} ${GRPC_SRCS} ${GRPC_HDRS})
target_link_libraries(client PRIVATE protobuf::libprotobuf gRPC::grpc++)
2.6 编译与运行
  1. 安装依赖

    确保已安装Protobuf和gRPC库。可以参考以下步骤进行安装:

    # 安装gRPC及其依赖
    git clone -b v1.53.0 https://github.com/grpc/grpc
    cd grpc
    git submodule update --init
    mkdir -p build
    cd build
    cmake .. -DCMAKE_BUILD_TYPE=Release
    make -j4
    sudo make install
    sudo ldconfig
    
  2. 编译项目

    在项目根目录下创建构建目录并编译:

    mkdir build
    cd build
    cmake ..
    make
    
  3. 运行服务器

    在一个终端中启动服务器:

    ./server
    

    输出:

    Server listening on 0.0.0.0:50051
    
  4. 运行客户端

    在另一个终端中运行客户端:

    ./client
    

    输出:

    User ID: 1
    Name: Alice
    Email: alice@example.com
    
2.7 关键组件解析

在这里插入图片描述
以下是各组件在上述实现中的作用:

  1. client(客户端)

    • 客户端代码位于 client.cpp,通过 UserServiceClient 类发起RPC请求。
    • serialize(序列化):Protobuf自动处理消息的序列化,将 GetUserRequest 转换为字节流。
    • deserialize(反序列化):接收 GetUserResponse 字节流并转换为 User 对象。
  2. server(服务器)

    • 服务器代码位于 server.cpp,通过 UserServiceImpl 类实现服务逻辑。
    • serialize/deserialize:Protobuf自动处理接收的请求数据反序列化和响应数据序列化。
  3. protocol stack(协议栈)

    • 在本示例中,gRPC基于HTTP/2协议栈处理网络通信细节,负责数据的传输和连接管理。
  4. idl(接口定义语言)

    • user_service.proto 文件定义了服务接口和消息结构,作为IDL文件。
  5. compiler(编译器)

    • protoc 编译器与gRPC插件一起,将.proto文件编译生成C++代码,生成消息类和服务骨架代码。
  6. skeleton(骨架代码)

    • 生成的 user_service.grpc.pb.huser_service.grpc.pb.cc 文件包含服务接口的骨架代码。
    • 服务器实现类 UserServiceImpl 继承自生成的骨架类,完成业务逻辑。
    • 客户端通过生成的 UserService::Stub 类进行远程调用,隐藏了底层的通信细节。
2.8 序列化与反序列化的实现

在上述示例中,序列化与反序列化过程由gRPC和Protobuf库自动处理,开发者无需手动编写相关代码。然而,理解这一过程对于优化和调试至关重要。

  • 序列化

    • 客户端在调用 stub_->GetUser(&context, request, &response) 时,Protobuf将 GetUserRequest 对象序列化为二进制格式,通过gRPC协议栈发送到服务器。
  • 反序列化

    • 服务器接收到字节流后,Protobuf将其反序列化为 GetUserRequest 对象,并传递给 GetUser 方法进行处理。
  • 响应过程

    • 服务器将 GetUserResponse 对象序列化后,通过gRPC协议栈发送回客户端,客户端再将其反序列化为 GetUserResponse 对象。

三、关键实现与解析

通过上述代码示例,我们可以看到Protobuf和RPC的高效结合体现在以下几个方面:

  1. 自动化生成代码:通过Protobuf定义的IDL文件(.proto),可以自动生成C++类,省去手动编写通信代码的复杂性。

  2. 数据序列化与反序列化:Protobuf通过高效的二进制格式,减少了数据传输的开销,保证了性能和数据传输的一致性。

  3. RPC框架的透明性:开发者不必关心底层通信协议,gRPC框架负责数据的传输和连接管理,使得程序员能够像调用本地函数一样完成远程调用。

四、工程实践中的最佳实践

  1. 代码生成自动化:集成Protobuf的代码生成步骤至构建系统,确保.proto文件变更后,自动生成与其匹配的代码。

  2. 性能调优:对于大规模消息传输,可考虑使用Protobuf的流式解析功能,并优化网络带宽。

  3. 版本兼容性:在设计Protobuf消息结构时,尽量避免破坏兼容性,确保后续扩展性。例如,避免删除字段或改变字段编号。

  4. 错误处理机制:在客户端和服务器端实现完备的错误处理机制,确保网络异常和序列化失败时的回退策略。

五、总结

Protobuf与RPC框架的结合为现代分布式系统提供了一种高效的通信方案。在C++工程中,通过合理设计和使用Protobuf进行数据序列化,再借助gRPC完成远程过程调用,可以大幅提升通信效率,并简化系统的开发与维护。

附录:常用Protobuf与gRPC命令

  • 编译Protobuf文件

    protoc --cpp_out=. user_service.proto
    
  • 编译gRPC Protobuf文件

    protoc --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` user_service.proto
    
  • 生成所有代码(包括消息和gRPC服务)

    protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` user_service.proto
    

参考资料

  • Protocol Buffers 官方文档
  • gRPC 官方文档
  • Protobuf 编码原理详解
  • 0voice · GitHub

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

想不到!手机壁纸变现项目,有人 3 个月怒赚 180000+(附教程)

同学们&#xff01;今天无意间发现了一个超级有潜力的变现数据账号。这个账号专注于制作 3D 立体膨胀壁纸&#xff0c;我实在是忍不住要和大家分享。 这个账号的笔记内容非常简洁&#xff0c;主要就是展示壁纸作品。然而&#xff0c;就是这样简单的内容&#xff0c;却在短短 8…

介绍我经常使用的两款轻便易用的 JSON 工具

第一款是 Chrome Extension&#xff0c;名叫 JSON Viewer Pro&#xff0c;可以在 Chrome 应用商店下载&#xff1a; 点击右上角的 JSON Input&#xff0c;然后可以直接把 JSON 字符串内容粘贴进去&#xff0c;也直接直接加载本地 JSON 文件。 可以在树形显示和图形显示两种模式…

淘宝自动下单退货RPA自动化脚本(已运行两个月)

使用AdsPower Browser写的两个自动化脚本&#xff0c;一个下单一个退货&#xff0c;我也不知道他为什么要做这个自动化脚本&#xff0c;运行2个月时间&#xff0c;还蛮稳定&#xff0c;可以多窗口并发运行! 下单指定淘宝商品连接&#xff0c;执行下单RPA脚本实现自动操作。 退…

模糊测试SFuzz亮相第32届中国国际信息通信展览会

9月25日&#xff0c;被誉为“中国ICT市场的创新基地和风向标”的第32届中国国际信息通信展在北京盛大开幕&#xff0c;本次展会将在为期三天的时间内&#xff0c;为信息通信领域创新成果、尖端技术和产品提供国家级交流平台。开源网安携模糊测试产品及相关解决方案精彩亮相&…

Flux最新ControlNet 高清修复模型测评,效果好速度快!

上一篇介绍了Jasper AI 发布了三个模型中的法线贴图&#xff0c;没看过的可以看一下哈&#xff1a; Flux目前最快ControlNet模型现身&#xff01;法线贴图详细测评 (chinaz.com) 这次再介绍一下另一个模型&#xff1a;升频器&#xff0c;可以有比较好的模糊修复效果&#xff…

一条命令Docker安装常用桌面linux系统含一些系统和应用

分类 一. opens use 15.5 desktop https://hub.docker.com/r/kasmweb/opensuse-15-desktop 这是我最近用的一个,稳定性和性能好过ubuntu,兼容性稍微差,部分依赖无法安装,部分软件运行不起来,界面比ubuntu的要好看.风格是win10的.提供一个开源的webVNC, 可选,但是桌面必定要用…

AIGC专栏16——CogVideoX-Fun V1.1版本详解 支持图文生视频与更大的动态性 为文生视频添加控制

AIGC专栏16——CogVideoX-Fun V1.1版本详解 支持图&文生视频与更大的动态性 为文生视频添加控制 学习前言相关地址汇总源码下载地址HF测试链接 CogVideoX-Fun V1.1详解技术储备Diffusion Transformer (DiT)Stable Diffusion 3EasyAnimate-I2V 算法细节V1.1特点参考图片添加…

20.1 分析pull模型在k8s中的应用,对比push模型

本节重点介绍 : push模型和pull模型监控系统对比为什么在k8s中只能用pull模型的k8s中主要组件的暴露地址说明 push模型和pull模型监控系统 对比下两种系统采用的不同采集模型&#xff0c;即push型采集和pull型采集。不同的模型在性能的考虑上是截然不同的。下面表格简单的说…

二、Spring Boot集成Spring Security之实现原理

Spring Boot集成Spring Security之实现原理 一、Spring Security实现原理概要介绍二、使用WebSecurityConfiguration向Spring容器中注册FilterChainProxy类型的对象springSecurityFilterChain1、未配置securityFilterChain过滤器链时使用默认配置用于生成默认securityFilterCha…

Java SE 总结

Java SE&#xff08;Standard Edition&#xff09;是Java编程语言的标准版本&#xff0c;提供了基础的编程环境和API&#xff0c;适用于开发和运行Java应用程序。下面是Java SE的几个重要方面的知识回顾与总结。 1. Java环境基础 具体可参考这里对三者的介绍 传送门 1.1 JVM…

后端-对表格数据进行添加、删除和修改

一、添加 要求&#xff1a; 按下添加按钮出现一个板块输入添加的数据信息&#xff0c;点击板块的添加按钮&#xff0c;添加&#xff1b;点击取消&#xff0c;板块消失。 实现&#xff1a; 1.首先&#xff0c;设计页面输入框格式&#xff0c;表格首行 2.从数据库里调数据 3.添加…

SpringBoot助力墙绘艺术市场创新

3 系统分析 当用户确定开发一款程序时&#xff0c;是需要遵循下面的顺序进行工作&#xff0c;概括为&#xff1a;系统分析–>系统设计–>系统开发–>系统测试&#xff0c;无论这个过程是否有变更或者迭代&#xff0c;都是按照这样的顺序开展工作的。系统分析就是分析系…

数字化智能工厂应用场景

数字化智能工厂的应用场景广泛&#xff0c;涵盖了多个行业和领域。以下是一些主要的应用场景&#xff1a; 一、制造业 汽车制造&#xff1a;数字化智能工厂在汽车制造业中得到了广泛应用。通过自动化生产线、机器人、物联网和人工智能等技术&#xff0c;汽车制造商能够实现高…

三分钟速览:Node.js 版本差异与关键特性解析

Node.js 是一个广泛使用的 JavaScript 运行时环境&#xff0c;允许开发者在服务器端运行 JavaScript 代码。随着技术的发展&#xff0c;Node.js 不断推出新版本&#xff0c;引入新特性和改进。了解不同版本之间的差异对于开发者来说至关重要。以下是一个快速指南&#xff0c;帮…

Redis篇(应用案例 - 附近商户)(持续更新迭代)

目录 一、GEO数据结构的基本用法 二、导入店铺数据到GEO 三、实现附近商户功能 一、GEO数据结构的基本用法 GEO就是Geolocation的简写形式&#xff0c;代表地理坐标。 Redis在3.2版本中加入了对GEO的支持&#xff0c;允许存储地理坐标信息&#xff0c;帮助我们根据经纬度来…

【高性能内存池】基本框架 + 固定长度内存池实现 1

高性能内存池 1. 基本框架2. 定长内存池的实现2.1 介绍定长内存池2.2 T* New()2.3 void Delete(T* obj) 3. 源码&#xff08;附赠测试&#xff09;4. 总结 1. 基本框架 高并发内存池主要由三个部分构成&#xff1a; 1.thread cache:用于小于256KB的内存的分配。线程缓存是每个…

opencv实战项目(三十):使用傅里叶变换进行图像边缘检测

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一&#xff0c;什么是傅立叶变换&#xff1f;二&#xff0c;图像处理中的傅立叶变换&#xff1a;三&#xff0c;傅里叶变换进行边缘检测&#xff1a; 一&#xff0c…

适合初学者的[JAVA]: 基础面试题

目录 说明 前言 String/StringBuffer/StringBuilder区别 第一点: 第二点: 总结&#xff1a; 反射机制 JVM内存结构 运行时数据区域被划分为5个主要组件&#xff1a; 方法区&#xff08;Method Area&#xff09; 堆区&#xff08;Heap Area&#xff09; 栈区&#x…

局部整体(七)利用python绘制圆形嵌套图

局部整体&#xff08;七&#xff09;利用python绘制圆形嵌套图 圆形嵌套图&#xff08; Circular Packing&#xff09;简介 将一组组圆形互相嵌套起来&#xff0c;以显示数据的层次关系&#xff0c;类似于矩形树图。数据集中每个实体都由一个圆表示&#xff0c;圆圈大小与其代…

Spring Task 调度任务

Spring Task是调度任务框架&#xff0c;通过配置&#xff0c;程序可以按照约定的时间自动执行代码逻辑&#xff0c;基于注解方式实现需要如下注解&#xff1a; Component 任务调度类交给Spring IOC容器管理EnableScheduling 启用 Spring 的定时任务&#xff08;Scheduling&…