总体流程1. 安装对应语言的 gRPC 工具链protoc、语言 gRPC 插件、运行时库。2. 编写并维护 客户端 / 服务端共识的.proto定义package、message、service/rpc方法及请求响应类型。3. 用protoc gRPC 插件生成代码消息类、客户端 stub、服务端接口/基类。(xx.pb.h/.cc、xx.grpc.pb.h/.cc)4. 请客户端创建 channel通过 stub 调用 RPC服务端实现 generated service 接口并注册启动5. 分别构建、联调客户端/服务端安装对应语言的 gRPC 工具链以安装C语言的gRPC 工具链# 1. 下载 vcpkg # 国内推荐 Gitee 极速下载镜像本次已验证可用同步略有延迟不影响安装 grpc cd D:\ git clone https://gitee.com/mirrors/vcpkg.git # 官方源备选若直连 GitHub 顺畅git clone https://github.com/microsoft/vcpkg.git # 2. 初始化生成 vcpkg.exe约 10 MB此步骤仍从 GitHub 下载失败对策见下方说明 cd D:\vcpkg .\bootstrap-vcpkg.bat -disableMetrics # 3. 一键安装 gRPC 及其全部依赖protobuf/abseil/c-ares/re2/OpenSSL 自动处理 # vcpkg 默认 triplet x64-windows → 自动挑机器上默认的编译工具编译了全部依赖库 .\vcpkg install grpc:x64-windows # 4. 验收 .\vcpkg list注意执行第3步时要结合Client/Server实际的编译器进行选择。例如我的机器的执行.\vcpkg install grpc:x64-windows 时会采用VS2022v143编译。而实际项目采用与Qt5.14.2匹配的MSVC142编译因此这里需要在 /triplets 下写入.cmake 并在执行时指定D:\gRPC\vcpkg\triplets\x64-windows-v142.cmake ├── set(VCPKG_TARGET_ARCHITECTURE x64) ← 编 64 位 ├── set(VCPKG_CRT_LINKAGE dynamic) ← CRT 用 /MD 动态运行库与 Qt 一致 ├── set(VCPKG_LIBRARY_LINKAGE dynamic) ← 库以 DLL 形态为主沿用标准 x64-windows 行为 └── set(VCPKG_PLATFORM_TOOLSET v142) ← ★ 关键行用 VS2019/v142 工具集编译 # 3. 一键安装 gRPC 及其全部依赖protobuf/abseil/c-ares/re2/OpenSSL 自动处理 cd D:\gRPC\vcpkg .\vcpkg install grpc:x64-windows-v142执行第3步时默认会从github下载依赖项。速度可能很慢此时可以终止(CtrlC)该过程执行并手动换源下载相应依赖项curl.exe -L --fail --retry 2 -o D:\gRPC\vcpkg\downloads\PowerShell-7.6.6-win-x64.zip https://ghproxy.net/https://github.com/PowerShell/PowerShell/releases/download/v7.6.6/PowerShell-7.6.6-win-x64.zip # 可提供的几个加速站 https://ghproxy.net/ https://gh-proxy.com/ https://ghproxy.homeboyc.cn/编写并维护 客户端 / 服务端共识的.protosyntax proto3; package demo; // 一条设备状态记录 message DeviceStatus { string device_id 1; // 设备编号 double temperature 2; // 温度摄氏度 bool online 3; // 在线状态 int64 timestamp 4; // 上报时间Unix 秒服务端生成 } // ---------- 写接口上报设备状态 ---------- message ReportStatusRequest { string device_id 1; double temperature 2; bool online 3; } message ReportStatusReply { int32 code 1; // 0成功1参数错误 string message 2; // 结果描述 DeviceStatus status 3; // 成功时返回落库后的完整状态含时间戳 } // ---------- 读接口查询设备最新状态 ---------- message QueryStatusRequest { string device_id 1; } message QueryStatusReply { int32 code 1; // 0成功1设备不存在 string message 2; DeviceStatus status 3; // 成功时返回 } service DeviceService { rpc ReportStatus (ReportStatusRequest) returns (ReportStatusReply); // 写 rpc QueryStatus (QueryStatusRequest) returns (QueryStatusReply); // 读 }# 本目录为 Client/Server 共享的接口契约目录由两端项目通过 add_subdirectory 引入。 # 职责把 demo.proto 生成为 C 代码并封装成 demo_proto 静态库供两端复用。 find_package(Protobuf CONFIG REQUIRED) find_package(gRPC CONFIG REQUIRED) set(PROTO_FILE ${CMAKE_CURRENT_SOURCE_DIR}/demo.proto) set(GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated) add_custom_command( OUTPUT ${GEN_DIR}/demo.pb.cc ${GEN_DIR}/demo.pb.h ${GEN_DIR}/demo.grpc.pb.cc ${GEN_DIR}/demo.grpc.pb.h COMMAND protobuf::protoc ARGS --proto_path${CMAKE_CURRENT_SOURCE_DIR} --cpp_out${GEN_DIR} --grpc_out${GEN_DIR} --pluginprotoc-gen-grpc$TARGET_FILE:gRPC::grpc_cpp_plugin ${PROTO_FILE} DEPENDS ${PROTO_FILE} COMMENT Running protoc on demo.proto ) # 封装成静态库。把生成的两个 .cc 编译进静态库。 add_library(demo_proto STATIC ${GEN_DIR}/demo.pb.cc ${GEN_DIR}/demo.grpc.pb.cc ) # 公开头文件路径。PUBLIC编译 demo_proto 本身时需要链接 demo_proto 的目标Client/Server自动继承这个 include 路径从而能 #include demo.pb.h。 target_include_directories(demo_proto PUBLIC ${GEN_DIR}) # 链接依赖。PUBLIC 传播依赖Client/Server 链接 demo_proto 时自动带上 gRPC 和 protobuf 的库与 include 路径调用方无需重复声明。 target_link_libraries(demo_proto PUBLIC gRPC::grpc protobuf::libprotobuf)Client/Server 的调用/实现以及生成gRPC代码Client 代码(stub调用方法) CMakeListcmake_minimum_required(VERSION 3.16) project(gRPCClient VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 源码统一使用 UTF-8 编码含中文文案MSVC 需显式声明避免乱码与 C4819 警告 if(MSVC) add_compile_options(/utf-8) endif() find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools) # 引入共享的 proto 契约目录生成 demo.pb.* / demo.grpc.pb.* 并编译为 demo_proto 静态库 add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../proto ${CMAKE_CURRENT_BINARY_DIR}/demo_proto_gen) set(TS_FILES gRPCClient_zh_CN.ts) set(PROJECT_SOURCES main.cpp widget.cpp widget.h ${TS_FILES} ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(gRPCClient MANUAL_FINALIZATION ${PROJECT_SOURCES} ) # Define target properties for Android with Qt 6 as: # set_property(TARGET gRPCClient APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) else() if(ANDROID) add_library(gRPCClient SHARED ${PROJECT_SOURCES} ) # Define properties for Android with Qt 5 after find_package() calls as: # set(ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android) else() add_executable(gRPCClient ${PROJECT_SOURCES} ) endif() qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) endif() target_link_libraries(gRPCClient PRIVATE Qt${QT_VERSION_MAJOR}::Widgets demo_proto) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an # explicit, fixed bundle identifier manually though. if(${QT_VERSION} VERSION_LESS 6.1.0) set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.gRPCClient) endif() set_target_properties(gRPCClient PROPERTIES ${BUNDLE_ID_OPTION} MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE ) include(GNUInstallDirs) install(TARGETS gRPCClient BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(gRPCClient) endif()// // widget.h // #ifndef WIDGET_H #define WIDGET_H #include QWidget #include memory #include grpcpp/grpcpp.h #include demo.grpc.pb.h class QLineEdit; class QDoubleSpinBox; class QCheckBox; class QLabel; class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); private slots: void onReportStatus(); // 写接口上报设备状态 void onQueryStatus(); // 读接口查询设备最新状态 private: QWidget *createReportGroup(); // 构建“上报设备状态”区域 QWidget *createQueryGroup(); // 构建“查询设备状态”区域 // gRPC 客户端channel 桩channel 线程安全可长期复用 std::shared_ptrgrpc::Channel m_channel; std::unique_ptrdemo::DeviceService::Stub m_stub; // 上报区控件 QLineEdit *m_reportDeviceId nullptr; QDoubleSpinBox *m_reportTemperature nullptr; QCheckBox *m_reportOnline nullptr; QLabel *m_reportResult nullptr; // 查询区控件 QLineEdit *m_queryDeviceId nullptr; QLabel *m_queryResult nullptr; }; #endif // WIDGET_H // // widget.cpp // #include widget.h #include QCheckBox #include QDoubleSpinBox #include QFormLayout #include QGroupBox #include QLabel #include QLineEdit #include QPushButton #include QVBoxLayout #include chrono namespace { // 服务端地址与 gRPCServer/main.cpp 中的监听地址保持一致 constexpr char kServerAddress[] localhost:50052; // 单次 RPC 超时时间秒避免服务端未启动时界面长时间卡住 constexpr int kRpcTimeoutSeconds 3; } // namespace Widget::Widget(QWidget *parent) : QWidget(parent) { setWindowTitle(tr(gRPC 设备状态演示 - 客户端)); // 创建 channel 与桩channel 创建是非阻塞的首次调用时才真正建立连接 m_channel grpc::CreateChannel(kServerAddress, grpc::InsecureChannelCredentials()); m_stub demo::DeviceService::NewStub(m_channel); auto *layout new QVBoxLayout(this); layout-addWidget(createReportGroup()); layout-addWidget(createQueryGroup()); layout-addStretch(); resize(520, 420); } Widget::~Widget() {} QWidget *Widget::createReportGroup() { auto *group new QGroupBox(tr(写接口上报设备状态), this); auto *form new QFormLayout(group); m_reportDeviceId new QLineEdit(group); m_reportDeviceId-setPlaceholderText(tr(例如DEV-001)); m_reportTemperature new QDoubleSpinBox(group); m_reportTemperature-setRange(-100.0, 200.0); m_reportTemperature-setDecimals(1); m_reportTemperature-setSuffix(tr( ℃)); m_reportTemperature-setValue(25.0); m_reportOnline new QCheckBox(tr(在线), group); m_reportOnline-setChecked(true); auto *reportButton new QPushButton(tr(上报), group); m_reportResult new QLabel(tr(尚未上报), group); m_reportResult-setWordWrap(true); m_reportResult-setTextInteractionFlags(Qt::TextSelectableByMouse); form-addRow(tr(设备编号), m_reportDeviceId); form-addRow(tr(温度), m_reportTemperature); form-addRow(m_reportOnline); form-addRow(reportButton); form-addRow(tr(结果), m_reportResult); connect(reportButton, QPushButton::clicked, this, Widget::onReportStatus); return group; } QWidget *Widget::createQueryGroup() { auto *group new QGroupBox(tr(读接口查询设备最新状态), this); auto *form new QFormLayout(group); m_queryDeviceId new QLineEdit(group); m_queryDeviceId-setPlaceholderText(tr(例如DEV-001)); auto *queryButton new QPushButton(tr(查询), group); m_queryResult new QLabel(tr(暂无查询结果), group); m_queryResult-setWordWrap(true); m_queryResult-setTextInteractionFlags(Qt::TextSelectableByMouse); form-addRow(tr(设备编号), m_queryDeviceId); form-addRow(queryButton); form-addRow(tr(结果), m_queryResult); connect(queryButton, QPushButton::clicked, this, Widget::onQueryStatus); return group; } void Widget::onReportStatus() { const QString deviceId m_reportDeviceId-text().trimmed(); if (deviceId.isEmpty()) { m_reportResult-setText(tr(请先填写设备编号)); return; } demo::ReportStatusRequest request; request.set_device_id(deviceId.toStdString()); request.set_temperature(m_reportTemperature-value()); request.set_online(m_reportOnline-isChecked()); demo::ReportStatusReply reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() std::chrono::seconds(kRpcTimeoutSeconds)); const grpc::Status status m_stub-ReportStatus(context, request, reply); if (!status.ok()) { // 传输层错误服务端未启动、超时、网络中断等 m_reportResult-setText(tr(RPC 调用失败%1) .arg(QString::fromStdString(status.error_message()))); return; } if (reply.code() ! 0) { // 业务层错误服务端以 code/message 表达 m_reportResult-setText(tr(服务端拒绝%1) .arg(QString::fromStdString(reply.message()))); return; } const demo::DeviceStatus s reply.status(); m_reportResult-setText(tr(上报成功设备 %1温度 %2 ℃%3时间戳 %4) .arg(QString::fromStdString(s.device_id())) .arg(s.temperature()) .arg(s.online() ? tr(在线) : tr(离线)) .arg(s.timestamp())); } void Widget::onQueryStatus() { const QString deviceId m_queryDeviceId-text().trimmed(); if (deviceId.isEmpty()) { m_queryResult-setText(tr(请先填写设备编号)); return; } demo::QueryStatusRequest request; request.set_device_id(deviceId.toStdString()); demo::QueryStatusReply reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() std::chrono::seconds(kRpcTimeoutSeconds)); const grpc::Status status m_stub-QueryStatus(context, request, reply); if (!status.ok()) { m_queryResult-setText(tr(RPC 调用失败%1) .arg(QString::fromStdString(status.error_message()))); return; } if (reply.code() ! 0) { m_queryResult-setText(tr(查询失败%1) .arg(QString::fromStdString(reply.message()))); return; } const demo::DeviceStatus s reply.status(); m_queryResult-setText(tr(设备编号%1\n温度%2 ℃\n状态%3\n上报时间戳%4) .arg(QString::fromStdString(s.device_id())) .arg(s.temperature()) .arg(s.online() ? tr(在线) : tr(离线)) .arg(s.timestamp())); } // // main.cpp // #include widget.h #include QApplication #include QLocale #include QTranslator int main(int argc, char *argv[]) { QApplication a(argc, argv); QTranslator translator; const QStringList uiLanguages QLocale::system().uiLanguages(); for (const QString locale : uiLanguages) { const QString baseName gRPCClient_ QLocale(locale).name(); if (translator.load(:/i18n/ baseName)) { a.installTranslator(translator); break; } } Widget w; w.show(); return a.exec(); }Server 代码(service具体类实现) CMakeListcmake_minimum_required(VERSION 3.16) project(gRPCServer LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 源码统一使用 UTF-8 编码含中文文案MSVC 需显式声明避免乱码与 C4819 警告 if(MSVC) add_compile_options(/utf-8) endif() find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core LinguistTools) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools) # 引入共享的 proto 契约目录生成 demo.pb.* / demo.grpc.pb.* 并编译为 demo_proto 静态库 add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../proto ${CMAKE_CURRENT_BINARY_DIR}/demo_proto_gen) set(TS_FILES gRPCServer_zh_CN.ts) add_executable(gRPCServer main.cpp deviceserviceimpl.h deviceserviceimpl.cpp ${TS_FILES} ) target_link_libraries(gRPCServer Qt${QT_VERSION_MAJOR}::Core demo_proto) if(COMMAND qt_create_translation) qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) else() qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) endif() include(GNUInstallDirs) install(TARGETS gRPCServer LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )// // deviceserviceimpl.h // #ifndef DEVICESERVICEIMPL_H #define DEVICESERVICEIMPL_H #include QHash #include QString #include mutex #include demo.grpc.pb.h // DeviceService 服务端实现内存存储各设备的最新状态 class DeviceServiceImpl final : public demo::DeviceService::Service { public: // 写接口上报设备状态服务端打时间戳并覆盖保存 grpc::Status ReportStatus(grpc::ServerContext *context, const demo::ReportStatusRequest *request, demo::ReportStatusReply *reply) override; // 读接口按设备编号查询最新状态 grpc::Status QueryStatus(grpc::ServerContext *context, const demo::QueryStatusRequest *request, demo::QueryStatusReply *reply) override; private: std::mutex m_mutex; // gRPC 服务端多线程处理请求需保护共享数据 QHashQString, demo::DeviceStatus m_status; // device_id - 最新状态 }; #endif // DEVICESERVICEIMPL_H // // deviceserviceimpl.cpp // #include deviceserviceimpl.h #include QDateTime #include iostream grpc::Status DeviceServiceImpl::ReportStatus(grpc::ServerContext *context, const demo::ReportStatusRequest *request, demo::ReportStatusReply *reply) { Q_UNUSED(context); const QString deviceId QString::fromStdString(request-device_id()).trimmed(); if (deviceId.isEmpty()) { reply-set_code(1); reply-set_message(QStringLiteral(设备编号不能为空).toStdString()); return grpc::Status::OK; } demo::DeviceStatus status; status.set_device_id(deviceId.toStdString()); status.set_temperature(request-temperature()); status.set_online(request-online()); status.set_timestamp(QDateTime::currentSecsSinceEpoch()); { std::lock_guardstd::mutex lock(m_mutex); m_status.insert(deviceId, status); // 同设备保留最新状态 } reply-set_code(0); reply-set_message(QStringLiteral(上报成功).toStdString()); *reply-mutable_status() status; std::cout [gRPCServer] ReportStatus: device_id deviceId.toStdString() , temperature request-temperature() , online (request-online() ? true : false) std::endl; return grpc::Status::OK; } grpc::Status DeviceServiceImpl::QueryStatus(grpc::ServerContext *context, const demo::QueryStatusRequest *request, demo::QueryStatusReply *reply) { Q_UNUSED(context); const QString deviceId QString::fromStdString(request-device_id()).trimmed(); demo::DeviceStatus status; bool found false; { std::lock_guardstd::mutex lock(m_mutex); const auto it m_status.constFind(deviceId); if (it ! m_status.constEnd()) { status it.value(); found true; } } if (!found) { reply-set_code(1); reply-set_message(QStringLiteral(设备不存在).toStdString()); std::cout [gRPCServer] QueryStatus: device_id deviceId.toStdString() - not found std::endl; return grpc::Status::OK; } reply-set_code(0); reply-set_message(QStringLiteral(查询成功).toStdString()); *reply-mutable_status() status; std::cout [gRPCServer] QueryStatus: device_id deviceId.toStdString() - found std::endl; return grpc::Status::OK; } // // main.cpp // #include deviceserviceimpl.h #include QCoreApplication #include QLocale #include QTranslator #include grpcpp/grpcpp.h #include iostream #include memory #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # define NOMINMAX # include windows.h #endif int main(int argc, char *argv[]) { #ifdef _WIN32 // 控制台切换为 UTF-8 输出避免中文日志乱码 SetConsoleOutputCP(CP_UTF8); #endif QCoreApplication a(argc, argv); QTranslator translator; const QStringList uiLanguages QLocale::system().uiLanguages(); for (const QString locale : uiLanguages) { const QString baseName gRPCServer_ QLocale(locale).name(); if (translator.load(:/i18n/ baseName)) { a.installTranslator(translator); break; } } // 启动 gRPC 服务监听 0.0.0.0:50052注册 DeviceService 实现 DeviceServiceImpl service; grpc::ServerBuilder builder; builder.AddListeningPort(0.0.0.0:50052, grpc::InsecureServerCredentials()); builder.RegisterService(service); std::unique_ptrgrpc::Server server builder.BuildAndStart(); if (!server) { std::cerr [gRPCServer] Failed to start server on 0.0.0.0:50052 std::endl; return 1; } std::cout [gRPCServer] listening on 0.0.0.0:50052 ... std::endl; server-Wait(); return 0; }