C++ 搭建一个双向多线程的GRPC通信服务框架

ops/2025/1/18 16:12:24/

文章目录

      • 功能点
      • 服务端
      • 客户端
      • 服务端线程
      • 客户端线程
      • 心跳机制
      • 服务创建
      • 总结

功能点

  • 双向通信:即指程序既有客服端又有服务端,以处理复杂的需求
  • 客户端信息线程处理:程序客户端发出某个请求后,应开辟其他线程处理,防止等待消息的时候主程序卡死
  • 心跳机制:若是下位的客服端一直向服务端推送消息(没有就单独设置一个心跳函数),那么可以监听这个函数,若是多少秒内没有收到消息即为断链
  • protobufgrpccmakelist编译与链接

服务端

  • 在继承grpc服务的同时,我们也继承qtqobject这样可以使用信号与槽的机制让其他类的到信息
  • 若是不是使用qt用不了信号机制,那么可以使用观察与订阅的设计模式去监听信息变化
class GrpcServer final : public QObject, public Scheduling::HMISend::Service
{Q_OBJECT
public:GrpcServer();/// 系统信息grpc::Status SystemStatus(grpc::ServerContext *context, const SystemStatusReq *request,SystemStatusResp *response);
signals:void systemStatusReceived(const QString &str);
};
grpc::Status GrpcServer::SystemStatus(grpc::ServerContext *context, const SystemStatusReq *request, SystemStatusResp *response)
{QString json_info = QString::fromStdString(request->json_info());emit systemStatusReceived(json_info);return Status::OK;
}

客户端

  • 客户端比较简单,实例一个类,定义好你在proto中的函数并调用对应的函数就行,同时客服端也是可以处理服务端返回的消息的
    std::pair<bool, std::string> GrpcClient::ScheduleTask(int32_t task, const std::string &cardata)
    {SchedulingRequest request;request.set_task(task);request.set_carbon_data(cardata);SchedulingResponse response;ClientContext context;Status status = stub_->Scheduling(&context, request, &response);std::pair<bool, std::string> result;if (status.ok()){if (response.abnormal() == "abnormal"){result.first = false;}else if (response.abnormal() == "normal"){result.first = true;}result.second = response.json_info();}else{result.first = false;result.second = "";}return result;
    }
    

服务端线程

  • 可继承QThread,将服务端加入其中,一直监听下位机的信息。注意程序终止后对线程的释放,和当grpc为正常链接时的异常处理
    ServerThread::ServerThread(QSharedPointer<GrpcServer> service, QObject *parent): QThread(parent), m_service(service)
    {
    }void ServerThread::run()
    {std::string server_address("0.0.0.0:50055");ServerBuilder builder;builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());builder.RegisterService(m_service.data());std::unique_ptr<Server> server;try{server = builder.BuildAndStart();if (!server){qWarning() << "GRPC服务未启动!请检查IP或者端口是否正确!";return;}}catch (const std::exception &e){qWarning() << "异常: " << e.what();return;}exec();if (server){server->Shutdown();server->Wait();}
    }ServerThread::~ServerThread()
    {if (isRunning()){quit();wait();}
    }

客户端线程

  • 将客户端将其加入其中进行管理,每当有需求,比如点击一个按钮时,就创建一个线程去处理这个需求,再利用信号机制,来及时处理返回信息
    void GrpcTaskHandler::executeTask(int32_t task, const std::string &cardata)
    {QThread *thread = QThread::create([=](){auto result = m_client->ScheduleTask(task,cardata);emit taskFinished(task, result); });thread->start();connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    }
    

心跳机制

  • 创建一个计时器,定义一个时间,每次服务端调用某个函数时更新这个时间,达到心跳检测机制
    void Widget::initUI()
    {// 心跳检测QTimer *timer2 = new QTimer(this);connect(timer2, &QTimer::timeout, this, &Widget::checkPlcStatusDataCallback);timer2->start(2000);// 初始化最后一次调用时间lastCallbackTime = QDateTime::currentDateTime();
    }void Widget::checkPlcStatusDataCallback()
    {QDateTime currentTime = QDateTime::currentDateTime();int elapsed = lastCallbackTime.msecsTo(currentTime);// 检查是否在过去的一段时间内回调函数被调用bool g = elapsed > 1800; // 不能为2000的原因是计时器有点误差存在if (!g){///}else{///}
    }
    

服务创建

  • 建议客户端服务端都使用智能指针管理,客服端可加入一个保活。

    void Widget::initServerClint()
    {auto portIP = "127.0.0.1:50052";grpc::ChannelArguments channel_args;channel_args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 10000);          // 10秒,发起 KeepAlive ping 的间隔channel_args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 5000);        // 5秒,等待 KeepAlive 响应的超时时间channel_args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1); // 即使没有活跃的 RPC,也允许发送 KeepAlive pingauto channel = grpc::CreateCustomChannel(portIP, grpc::InsecureChannelCredentials(), channel_args);m_grpcTaskHandler = new GrpcTaskHandler(std::shared_ptr<GrpcClient>(new GrpcClient(channel)), this);m_services = QSharedPointer<GrpcServer>(new GrpcServer());m_thread = QSharedPointer<ServerThread>(new ServerThread(m_services, this));// 连接信号和槽connect(m_services.data(), &GrpcServer::systemStatusReceived,[=](){ lastCallbackTime = QDateTime::currentDateTime(); });connect(m_services.data(), &GrpcServer::logInfoReceived,[=]() {});connect(m_services.data(), &GrpcServer::logInfoReceived,[=]() {});connect(m_services.data(), &GrpcServer::autoCalibReceived,[=]() {});connect(m_grpcTaskHandler, &GrpcTaskHandler::taskFinished, this, &Widget::handleToolMaintenanceResult);// 启动服务m_thread->start();
    }

    总结

    • 知识理应共享,源码在此
    • 这个proto文件的cmakelist编译还是挺难写的需要用到官方的grpc.cmake
      然后在cmakelist中还需要在实现一个function函数,有兴趣的可以自己琢磨
    • 上述通信服务的框架应该是比较完善的了,够应付一般的场景了。
    • protogrpc的安装,可以看我的这篇文章.

http://www.ppmy.cn/ops/151133.html

相关文章

MarsCode青训营打卡Day1(2025年1月14日)|稀土掘金-16.最大矩形面积问题

资源引用&#xff1a; 最大矩形面积问题 - MarsCode 打卡小记录&#xff1a; 今天是开营第一天&#xff0c;和小伙伴们组成了8人的团队&#xff0c;在接下来的数十天里相互监督&#xff0c;打卡刷题&#xff01; 稀土掘金-16.最大矩形面积问题&#xff08;16.最大矩形面积问题…

jenkins-node节点配置

一.简述&#xff1a; Jenkins有一个很强大的功能&#xff1a; 即&#xff1a;支持分布式构建(jenkins配置中叫节点(node),也被称为slave)。分布式构建通常是用来吸收额外的负载。通过动态添加额外的机器应对构建作业中的高峰期&#xff0c;或在特定操作系统或环境运行特定的构建…

【PromptCoder + v0.dev】:前端开发的智能加速器

【PromptCoder v0.dev】&#xff1a;前端开发的智能加速器 在当今快节奏的软件开发环境中&#xff0c;前端开发者面临着将设计稿快速转化为可运行代码的巨大挑战。每一个像素的完美呈现都需要精确的代码编写&#xff0c;这不仅耗时&#xff0c;而且容易出错。幸运的是&#x…

《探秘火焰目标检测开源模型:智能防火的科技利刃》

一、引言 火灾&#xff0c;如同隐藏在暗处的恶魔&#xff0c;时刻威胁着人们的生命财产安全与社会的稳定发展。无论是澳大利亚那场肆虐数月、烧毁约1860万公顷土地、造成近30亿只动物死亡或流离失所的森林大火&#xff0c;还是美国加州频繁爆发、迫使大量居民撤离家园、带来巨额…

浅谈云计算16 | 存储虚拟化技术

存储虚拟化技术 一、块级存储虚拟化基础2.1 LUN 解析2.1.1 LUN 概念阐释2.1.2 LUN 功能特性 2.2 Thick LUN与Thin LUN2.2.1 Thick LUN特性剖析2.2.2 Thin LUN特性剖析 三、块级存储虚拟化技术实现3.1 基于主机的实现方式3.1.1 原理阐述3.1.2 优缺点评估 3.2 基于存储设备的实现…

【JavaEE】Spring Web MVC

目录 一、Spring Web MVC简介 1.1 MVC简介1.2 Spring MVC1.3 RequestMapping注解1.3.1 使用1.3.2 RequestMapping的请求设置 1.3.2.1 方法11.3.2.2 方法2 二、Postman介绍 2.1 创建请求2.2 界面如下&#xff1a;2.3 传参介绍 一、Spring Web MVC简介 官方文档介绍&#xff…

Redis超详细入门教程(基础篇)

目录 一、什么是Redis 二、安装Redis 1、Windows系统安装 2、Linux系统安装 三、Redis通用命令 四、Redis基本命令 五、五种数据结构类型 5.1、String类型 5.2、List集合类型 5.3、Set集合类型 5.4、Hash集合类型 5.5、Zset有序集合类型 六、总结 一、什么是Redi…

在ES6模块中导入和导出

在ES6模块中导入和导出 以最简单的例子举例 //shoppingCart.js //导出模块 console.log(导出模块);//script.js //导出模块 import ./shoppingCart.js; console.log(导入模块);所以要导入其他模块必须指定类型 <script type"Modules" defer src"script.js&…