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

目录

      • 一、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.mfbz.cn/a/885936.html

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

相关文章

Redis: Sentinel哨兵监控架构及环境搭建

概述 在主从模式下&#xff0c;我们通过从节点只读模式提高了系统的并发能力并发不断增加&#xff0c;只需要扩展从节点即可&#xff0c;只要主从服务器之间&#xff0c;网络连接正常主服务器就会将写入自己的数据同步更新给从服务器&#xff0c;从而保证主从服务器的数据相同…

网络安全 DVWA通关指南 DVWA Weak Session IDs(弱会话)

DVWA Weak Session IDs&#xff08;弱会话&#xff09; 文章目录 DVWA Weak Session IDs&#xff08;弱会话&#xff09;Low LevelMedium LevelHigh LevelImpossible Level 参考文献 WEB 安全靶场通关指南 相关阅读 Brute Force (爆破) Command Injection&#xff08;命令注入…

C# 无边框窗体,加阴影效果、多组件拖动、改变大小等功能完美实现优化版效果体验

一、预览效果 国庆节第一天,祝祖国繁荣昌盛! 1.1 效果图 (WinForm无边框窗体,F11可全屏) 拖动窗体时半透明效果(拖动时参考窗体后面释放位置) 说明:本功能的实现基于网友的原型完善而来,更多代码可以参考他的文章 h

Golang | Leetcode Golang题解之第449题序列化和反序列化二叉搜索树

题目&#xff1a; 题解&#xff1a; type Codec struct{}func Constructor() (_ Codec) { return }func (Codec) serialize(root *TreeNode) string {arr : []string{}var postOrder func(*TreeNode)postOrder func(node *TreeNode) {if node nil {return}postOrder(node.Le…

量产小妙招---向量间的Project和Product

1 背景 在日常工作中&#xff0c;不管是在感知或者规控&#xff0c;或者其它的模块中&#xff0c;经常需要处理两个向量之间的关系&#xff0c;这就引入了本篇博客和读者朋友们讨论的一个话题&#xff1a;Project和Product。 2 Project和Product 向量间的Project和Product在定义…

C++语言学习(2): name lookup 的概念

何谓 name lookup C 中很重要的一个概念&#xff1a;name lookup。 当编译器在遇到一个 name 的时候&#xff0c; 会做查找&#xff08;lookup&#xff09;&#xff0c;会把引入这个 name 的声明和它关联起来&#xff0c;具体来说&#xff0c;又包含两种类型的 lookup&#xf…

【学习笔记】手写 Tomcat 八

目录 一、NIO 1. 创建 Tomcat NIO 类 2. 启动 Tomcat 3. 测试 二、解析请求信息 三、响应数据 创建响应类 修改调用的响应类 四、完整代码 五、测试 六、总结 七、获取全部用户的功能 POJO 生成 POJO 1. 在 Dao 层定义接口 2. 获取用户数据 3. 在 Service 层定…

ArcGIS与ArcGIS Pro去除在线地图服务名单

我们之前给大家分享了很多在线地图集&#xff0c;有些地图集会带有制作者信息&#xff0c;在布局制图的时候会带上信息影响出图美观。 一套GIS图源集搞定&#xff01;清新规划底图、影像图、境界、海洋、地形阴影图、导航图 比如ArcGIS&#xff1a; 比如ArcGIS Pro&#xff1a…

.Net 基于IIS部署blazor webassembly或WebApi

1.安装IIS(若安装&#xff0c;请忽略) 选择:控制面板–>程序–>程序和功能 选择:启动或关闭Windows功能&#xff0c;勾选相关项&#xff0c;再点击确定即可。 2.安装Hosting Bundle 以.net6为例&#xff0c;点击连接https://dotnet.microsoft.com/en-us/download/dot…

zabbix7.0创建自定义模板的案例详解(以监控httpd服务为例)

前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) 创建模板 模板组直接写一个新的&#xff0c;不用选择 通过名称查找模板&#xf…

详解CSS中的伪元素

4.3 伪元素 可以把样式应用到文档树中根本不存在的元素上。 ::first-line 文本中的第一行 ::first-letter 文本中的第一个字母 ::after 元素之后添加 ::before 元素之前 代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8&q…

测试用例的举例

1. 基于测试公式设计测试用例 通过功能&#xff0c;性能&#xff0c;安全性&#xff0c;界面&#xff0c;安全性&#xff0c;易用&#xff0c;兼容对于一个水杯进行测试用例的设计&#xff1b; 对于一个软件的测试用例设计&#xff1a; 功能&#xff1a;软件本质上能够用来干什…

本科生已不够 AI公司雇佣各领域专家训练大模型

9月29日消息&#xff0c;人工智能模型的性能在很大程度上依赖于其训练数据的质量。传统方法通常是雇用大量低成本劳动力对图像、文本等数据进行标注&#xff0c;以满足模型训练的基本需求。然而&#xff0c;这种方式容易导致模型在理解和生成信息时出现“幻觉”现象&#xff0c…

【递归】11. leetcode 129 求根节点到叶节点数字之和

1 题目描述 题目链接&#xff1a; 求根节点到叶节点数字之和 2 解答思路 第一步&#xff1a;挖掘出相同的子问题 &#xff08;关系到具体函数头的设计&#xff09; 第二步&#xff1a;只关心具体子问题做了什么 &#xff08;关系到具体函数体怎么写&#xff0c;是一个宏观…

追随 HarmonyOS NEXT,Solon v3.0 将在10月8日发布

Solon &#xff08;开放原子开源基金会&#xff0c;孵化项目&#xff09;原计划10月1日发布 v3.0 正式版。看到 HarmonyOS NEXT 将在 10月8日启用公测&#xff0c;现改为10月8日发布以示庆贺。另外&#xff0c;Solon 将在2025年启动“仓颉”版开发&#xff08;届时&#xff0c;…

数据仓库的建设——从数据到知识的桥梁

数据仓库的建设——从数据到知识的桥梁 前言数据仓库的建设 前言 企业每天都在产生海量的数据&#xff0c;这些数据就像无数散落的珍珠&#xff0c;看似杂乱无章&#xff0c;但每一颗都蕴含着潜在的价值。而数据仓库&#xff0c;就是那根将珍珠串起来的线&#xff0c;它能够把…

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯&#xff08;IM&#xff09;系统和实时通信应用中&#xff0c;WebSocket作为一种高效的双向通信协议&#xff0c;得到了广泛应用。然而&#xff0c;在实际使用中&#xff0c;如何确保消息的可靠传输…

解决Windows远程桌面 “为安全考虑,已锁定该用户账户,原因是登录尝试或密码更改尝试过多,请稍后片刻再重试,或与系统管理员或技术支持联系“问题

根本原因就是当前主机被通过远程桌面输入了过多的错误密码&#xff0c;被系统锁定。这种情况多数是你的服务器远程桌面被人试图攻击了&#xff0c;不建议取消系统锁定策略。如果阿里云或者腾讯云主机&#xff0c;只需要在管理后台通过管理终端或者VNC登陆一次&#xff0c;锁定即…

哈希表(HashMap、HashSet)

文章目录 一、 什么是哈希表二、 哈希冲突2.1 为什么会出现冲突2.2 如何避免出现冲突2.3 出现冲突如何解决 三、模拟实现哈希桶/开散列&#xff08;整型数据&#xff09;3.1 结构3.2 插入元素3.3 获取元素 四、模拟实现哈希桶/开散列&#xff08;泛型&#xff09;4.1 结构4.2 插…

DC00025【含论文】基于协同过滤推荐算法springboot视频推荐管理系统

1、项目功能演示 DC00025【含文档】基于springboot短视频推荐管理系统协同过滤算法视频推荐系统javaweb开发程序设计vue 2、项目功能描述 短视频推荐系统分为用户和系统管理员两个角色 2.1 用户角色 1、用户登录、用户注册 2、视频中心&#xff1a;信息查看、视频收藏、点赞、…