C++使用grpc详例(使用CMake构建)

安装grpc(Ubuntu)

  1. 安装protobuf编译器和库

    sudo apt update
    sudo apt install -y protobuf-compiler libprotobuf-dev
    
  2. 安装grpc

    安装依赖项

    sudo apt install -y build-essential autoconf libtool pkg-config libsystemd-dev libssl-dev
    

    安装c++支持

    sudo apt install -y libgflags-dev libgtest-dev libc++-dev clang
    

    克隆grpc库(需要挂梯子,不然下载速度太慢)

    git clone https://github.com/grpc/grpc.git
    cd grpc
    

    在这里插入图片描述

    下载第三方库(挂梯子直接下就可以了,不要用国内镜像源,之前用了发现是陷阱,很多库没法下载)

    git submodule update --init
    

    在这里插入图片描述

    如果出现下载失败,进入third_party文件夹,将下载失败的库的文件夹删掉,重新执行以上命令即可

    下载完成后创建CMake对应的文件夹,并进入这个文件夹

    mkdir -p cmake/build
    cd cmake/build
    

    编译安装

    cmake ../..
    make
    # 安装到系统目录
    make install
    

C++使用grpc

  1. 编写.proto文件

    syntax = "proto3";
    
    package device_service;
    
    service DeviceService {
      // 返回一维字符串数组
      rpc GetDeviceStringList (DeviceNameListRequest) returns (DeviceNameListResponse) {}
      // 返回单个int值
      rpc GetDeviceSlaveCnt (DeviceSlaveCntRequest) returns (DeviceSlaveCntResponse) {}
      // 返回自定义结构体类型
      rpc GetDeviceInfo (DeviceInfoRequest) returns (DeviceInfoResponse) {}
      // 返回二维字符串数组
      rpc GetDeviceTableBySlaveId (DeviceTableBySlaveIdRequest) returns (DeviceTableBySlaveIdResponse) {}
    }
    
    // 设备列表信息
    message DeviceNameListRequest {
      string device_name = 1;
    }
    
    message DeviceNameListResponse {
      repeated string device_names = 1;
    }
    
    message DeviceInfoDetail {
      string ip = 1;
      int32 port = 2;
      string type = 3;
      bool server_status = 4;
      bool simulate_status = 5;
      bool plan_status = 6;
    }
    
    // 设备详细信息
    message DeviceInfoRequest {
      // 查询的设备名称
      string device_name = 1;
    }
    
    // 返回值是自定义结构体类型DeviceInfo
    message DeviceInfoResponse {
      DeviceInfoDetail info = 1;
    }
    
    // 获取设备从机数量
    message DeviceSlaveCntRequest {
      string device_name = 1;
    }
    
    message DeviceSlaveCntResponse {
      int32 slave_cnt = 1;
    }
    
    // 根据设备名称和从机id和测点信息 获取设备详细信息,回复是二维List
    message DeviceTableBySlaveIdRequest {
      string device_name = 1;
      int32 slave_id = 2;
      string point_name = 3;
    }
    
    message DeviceTableRow{
      repeated string row = 1;
    }
    
    message DeviceTableBySlaveIdResponse {
      DeviceTableRow head_data = 1;
      repeated DeviceTableRow table_data = 2;
    }
    
  2. 根据.proto文件,生成服务端和客户端代码

    生成客户端代码

    protoc --cpp_out=. device.proto
    

    生成服务端代码

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

    在这里插入图片描述

  3. 编写grpc服务端代码

    定义device_rpc_server.h文件,有以下几个接口

    #include <memory>
    #include <grpcpp/impl/codegen/server_context.h>
    #include <grpcpp/impl/codegen/status.h>
    #include "../proto/device.grpc.pb.h" // 由protoc生成的grpc服务头文件
    #include "../proto/device.pb.h"      // 由protoc生成的消息头文件
    using namespace device_service;
    
    class DeviceRpcServer final : public DeviceService::Service
    {
    public:
        DeviceRpcServer() = default;
        
        grpc::Status checkDevice(grpc::ServerContext *context, auto *request, auto *response);
        // 返回一维字符串列表
        grpc::Status GetDeviceStringList(grpc::ServerContext *context, const DeviceNameListRequest *request, DeviceNameListResponse *response);
        // 返回单个int类型
        grpc::Status GetDeviceSlaveCnt(grpc::ServerContext *context, const DeviceSlaveCntRequest *request, DeviceSlaveCntResponse *response);
        // 返回自定义结构体类型DeviceInfo
        grpc::Status GetDeviceInfo(grpc::ServerContext *context, const DeviceInfoRequest *request, DeviceInfoResponse *response);
        // 返回二维字符串列表
        grpc::Status GetDeviceTableBySlaveId(grpc::ServerContext *context, const DeviceTableBySlaveIdRequest *request, DeviceTableBySlaveIdResponse *response);
    
    private:
        std::map<std::string, bool> _deviceMap = {
            {"PCS1", true},
            {"PCS2", true},
            {"BMS1", true},
            {"BMS2", true},
        };
    };
    

    编写device_rpc_server.cpp文件

    1. 检查设备是否存在

      grpc::Status DeviceRpcServer::checkDevice(grpc::ServerContext *context, auto *request, auto *response)
      {
          // 获取request中的设备名称
          std::string device_name = request->device_name();
          auto device = _deviceMap.find(device_name);
          if (device == _deviceMap.end())
          {
              // 处理设备不存在的情况,返回错误状态
              return grpc::Status(grpc::StatusCode::NOT_FOUND, "未找到" + device_name + "设备");
          }
          // 设备存在,返回成功状态
          return grpc::Status::OK;
      }
      
    2. 返回一维字符串列表

      proto文件内容

      service DeviceService {
        // 返回一维字符串数组
        rpc GetDeviceStringList (DeviceNameListRequest) returns (DeviceNameListResponse) {}
      }
      
      // 设备列表信息
      message DeviceNameListRequest {
        string device_name = 1;
      }
      
      message DeviceNameListResponse {
        repeated string device_names = 1;
      }
      

      cpp文件内容

      grpc::Status DeviceRpcServer::GetDeviceStringList(grpc::ServerContext *context, const DeviceNameListRequest *request, DeviceNameListResponse *response)
      {
          std::vector<std::string> deviceNameList = {"PCS1", "PCS2", "BMS1", "BMS2"};
          for (const auto &name : deviceNameList)
          {
              // 这个函数是grpc自动生成的,名称与.proto文件里面的变量名有关
              response->add_device_names(name);
          }
          return grpc::Status::OK;
      }
      
    3. 返回单个int类型

      proto文件内容

      service DeviceService {
        // 返回单个int值
        rpc GetDeviceSlaveCnt (DeviceSlaveCntRequest) returns (DeviceSlaveCntResponse) {}
      }
      
      // 获取设备从机数量
      message DeviceSlaveCntRequest {
        string device_name = 1;
      }
      
      message DeviceSlaveCntResponse {
        int32 slave_cnt = 1;
      }
      

      cpp文件内容

      grpc::Status DeviceRpcServer::GetDeviceSlaveCnt(grpc::ServerContext *context, const DeviceSlaveCntRequest *request, DeviceSlaveCntResponse *response)
      {
          auto check = checkDevice(context, request, response);
          if (check.ok())
          {
              int slaveCnt = 1;
              response->set_slave_cnt(slaveCnt);
              return grpc::Status::OK;
          }
          else
          {
              return check;
          }
      }
      
    4. 返回自定义结构体类型

      proto文件内容

      service DeviceService {
        // 返回自定义结构体类型
        rpc GetDeviceInfo (DeviceInfoRequest) returns (DeviceInfoResponse) {}
      }
      
      message DeviceInfoDetail {
        string ip = 1;
        int32 port = 2;
        string type = 3;
        bool server_status = 4;
        bool simulate_status = 5;
        bool plan_status = 6;
      }
      
      // 设备详细信息
      message DeviceInfoRequest {
        // 查询的设备名称
        string device_name = 1;
      }
      
      // 返回值是自定义结构体类型DeviceInfo
      message DeviceInfoResponse {
        DeviceInfoDetail info = 1;
      }
      

      cpp文件内容

      grpc::Status DeviceRpcServer::GetDeviceInfo(grpc::ServerContext *context, const DeviceInfoRequest *request, DeviceInfoResponse *response)
      {
          std::string device_name = request->device_name();
          auto check = checkDevice(context, request, response);
          if (check.ok())
          {
              DeviceInfoDetail deviceInfo;
              std::string ip = "127.0.0.1";
              deviceInfo.set_ip(ip);
      
              int port = 502;
              deviceInfo.set_port(port);
      
              std::string type = "tcp";
              deviceInfo.set_type(type);
      
              bool serverStatus = true;
              deviceInfo.set_server_status(serverStatus);
      
              bool simulatorStatus = true;
              deviceInfo.set_simulate_status(simulatorStatus);
      
              bool planStatus = false;
              deviceInfo.set_plan_status(planStatus);
      		
              // 将自定义类型DeviceInfo拷贝到返回消息里面去
              response->mutable_info()->CopyFrom(deviceInfo);
              return grpc::Status::OK;
          }
          else
          {
              return check;
          }
      }
      
    5. 返回二维字符串列表

      proto文件

      service DeviceService {
        // 返回二维字符串数组
        rpc GetDeviceTableBySlaveId (DeviceTableBySlaveIdRequest) returns (DeviceTableBySlaveIdResponse) {}
      }
      
      // 根据设备名称和从机id和测点信息 获取设备详细信息,回复是二维List
      message DeviceTableBySlaveIdRequest {
        string device_name = 1;
        int32 slave_id = 2;
        string point_name = 3;
      }
      
      message DeviceTableRow{
        repeated string row = 1;
      }
      
      message DeviceTableBySlaveIdResponse {
        DeviceTableRow head_data = 1;
        repeated DeviceTableRow table_data = 2;
      }
      

      cpp文件

      grpc::Status DeviceRpcServer::GetDeviceTableBySlaveId(grpc::ServerContext *context, const DeviceTableBySlaveIdRequest *request, DeviceTableBySlaveIdResponse *response)
      {
          auto check = checkDevice(context, request, response);
          if (check.ok())
          {
              // 构造一组二维列表数据
              std::vector<std::string> headData = {
                  "设备名称",
                  "设备类型",
                  "设备IP",
                  "设备端口",
                  "设备状态",
                  "模拟状态",
                  "计划状态",
              };
              std::vector<std::vector<std::string>> tableData = {
                  {"PCS1", "tcp", "127.0.0.1", "502", "true", "true", "false"},
                  {"PCS2", "tcp", "127.0.0.1", "503", "true", "true", "false"},
                  {"BMS1", "tcp", "127.0.0.1", "504", "true", "true", "false"},
                  {"BMS2", "tcp", "127.0.0.1", "505", "true", "true", "false"},
              };
              
              // 返回列表头
              DeviceTableRow headRow;
              for (const auto &data : headData)
              {
                  headRow.add_row(data);
              }
              response->mutable_head_data()->CopyFrom(headRow);
      		
              // 返回列表数据
              for (const auto &data : tableData)
              {
                  DeviceTableRow table;
                  for (const auto &item : data)
                  {
                      table.add_row(item);
                  }
                  response->add_table_data()->CopyFrom(table);
              }
              return grpc::Status::OK;
          }
          else
          {
              return check;
          }
      }
      
    6. 服务端main函数代码

      #include <memory>
      #include <grpcpp/grpcpp.h>
      #include "device_rpc_server.h"
      
      using grpc::Server;
      using grpc::ServerBuilder;
      using grpc::ServerContext;
      using grpc::Status;
      
      void runServer()
      {
          std::string server_address("0.0.0.0:50051");
          DeviceRpcServer rpcServer;
      
          ServerBuilder builder;
          builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
          builder.RegisterService(&rpcServer);
          std::unique_ptr<Server> server(builder.BuildAndStart());
          std::cout << "Server listening on " << server_address << std::endl;
          server->Wait();
      }
      
      int main()
      {
          runServer();
          return 0;
      }
      
    7. CMakeLists.txt

      set(TARGET device_rpc_server)
      
      # rpc服务端
      file(GLOB_RECURSE RPC_SERVER_SOURCES "${CMAKE_SOURCE_DIR}/src/server/*.cpp" "${CMAKE_SOURCE_DIR}/src/proto/*.cc")
      
      # 查找 gRPC 和 Protobuf  
      find_package(Protobuf CONFIG REQUIRED)
      find_package(gRPC CONFIG REQUIRED)
      
      # 获取版本
      message(STATUS "Using protobuf-${Protobuf_VERSION}")
      message(STATUS "Using gRPC-${gRPC_VERSION}")
      
      # 包含 gRPC 和 Protobuf 的头文件  
      include_directories(${GRPC_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS})
      
      add_executable(${TARGET} ${RPC_SERVER_SOURCES})
      
      target_include_directories(${TARGET} PUBLIC ${GTEST_INCLUDE_DIRS})
      target_link_libraries(${TARGET} PUBLIC gtest gtest_main)
      target_link_libraries(${TARGET} PUBLIC gRPC::grpc++_reflection protobuf::libprotobuf)
      
  4. 编写grpc客户端代码

    定义device_rpc_client.h文件,有以下几个接口

    #include <grpcpp/grpcpp.h>
    #include <memory>
    #include <string>
    #include <vector>
    #include <map>
    #include "../proto/device.grpc.pb.h" // 由protoc生成的grpc服务头文件
    
    using grpc::Channel;
    using grpc::ClientContext;
    using grpc::Status;
    using namespace device_service;
    
    class DeviceServiceClient
    {
    public:
        DeviceServiceClient(std::shared_ptr<Channel> channel)
            : _stub(DeviceService::NewStub(channel)) {}
    
        std::vector<std::string> GetDeviceStringList();
        int GetDeviceSlaveCnt(const std::string &deviceName);
        std::map<std::string, std::string> GetDeviceInfo(const std::string &deviceName);
        std::vector<std::vector<std::string>> GetDeviceTableBySlaveId(const std::string &deviceName, int slaveId, std::string pointName);
    
    private:
        std::unique_ptr<DeviceService::Stub> _stub;
    };
    

    编写device_rpc_client.cpp文件

    1. 获取一维字符串列表

      std::vector<std::string> DeviceServiceClient::GetDeviceStringList()
      {
          ClientContext context;
          DeviceNameListRequest request;
          DeviceNameListResponse response;
      
          Status status = _stub->GetDeviceStringList(&context, request, &response);
      
          if (status.ok())
          {
              std::vector<std::string> device_names;
              for (const auto &name : response.device_names())
              {
                  device_names.push_back(name);
              }
              return device_names;
          }
          else
          {
              std::cerr << "GetDeviceStringList Failed: " << status.error_code() << ": " << status.error_message()
                        << std::endl;
              return {};
          }
      }
      
    2. 获取单个int值

      int DeviceServiceClient::GetDeviceSlaveCnt(const std::string &deviceName)
      {
          DeviceSlaveCntRequest request;
          request.set_device_name(deviceName);
          DeviceSlaveCntResponse response;
          ClientContext context;
          Status status = _stub->GetDeviceSlaveCnt(&context, request, &response);
          if (status.ok())
          {
              int slaveCnt = response.slave_cnt();
              return slaveCnt;
          }
          else
          {
              std::cerr << "GetDeviceSlaveCnt Failed:" << status.error_code() << ": " << status.error_message()
                        << std::endl;
              return -1;
          }
      }
      
    3. 获取自定义结构体信息

      std::map<std::string, std::string> DeviceServiceClient::GetDeviceInfo(const std::string &deviceName)
      {
          DeviceInfoRequest request;
          request.set_device_name(deviceName);
          DeviceInfoResponse response;
          ClientContext context;
          Status status = _stub->GetDeviceInfo(&context, request, &response);
          if (status.ok())
          {
              std::map<std::string, std::string> infoMap;
              infoMap["ip"] = response.info().ip();
              infoMap["port"] = std::to_string(response.info().port());
              infoMap["type"] = response.info().type();
              infoMap["server_status"] = std::to_string(response.info().server_status());
              infoMap["simulate_status"] = std::to_string(response.info().simulate_status());
              infoMap["plan_status"] = std::to_string(response.info().plan_status());
              return infoMap;
          }
          else
          {
              std::cerr << "GetDeviceInfo Failed: " << status.error_code() << ": " << status.error_message()
                        << std::endl;
              return {};
          }
      }
      
    4. 获取二维字符串列表

      std::vector<std::vector<std::string>> DeviceServiceClient::GetDeviceTableBySlaveId(const std::string &deviceName, int slaveId, std::string pointName)
      {
          DeviceTableBySlaveIdRequest request;
          DeviceTableBySlaveIdResponse response;
          ClientContext context;
          request.set_device_name(deviceName);
          request.set_slave_id(slaveId);
          request.set_point_name(pointName);
      
          Status status = _stub->GetDeviceTableBySlaveId(&context, request, &response);
          if (status.ok())
          {
              std::vector<std::vector<std::string>> table;
      
              // 处理列表头数据
              DeviceTableRow headRow = response.head_data();
              std::vector<std::string> head;
              for (const std::string &value : headRow.row())
              {
                  head.push_back(value);
              }
              table.push_back(head);
      
              // 处理列表数据
              for (int i = 0; i < response.table_data().size(); i++)
              {
                  DeviceTableRow tableRow = response.table_data().Get(i);
                  std::vector<std::string> row;
                  for (const std::string &value : tableRow.row())
                  {
                      row.push_back(value);
                  }
                  table.push_back(row);
              }
              return table;
          }
          else
          {
              std::cout << "GetDeviceTableBySlaveId error: " << status.error_message() << std::endl;
              return {};
          }
      }
      
    5. 客户端main函数代码

      #include "device_rpc_client.h"
      int main(int argc, char **argv)
      {
          DeviceServiceClient client(grpc::CreateChannel(
              "localhost:50051", grpc::InsecureChannelCredentials()));
      
          // 获取设备列表
          std::cout << "获取设备列表信息" << std::endl;
          std::vector<std::string> device_names = client.GetDeviceStringList();
          for (const auto &name : device_names)
          {
              std::cout << name << std::endl;
          }
          std::cout << std::endl;
      
          // 获取从机数量
          std::cout << "获取从机数量" << std::endl;
          int slaveCnt = client.GetDeviceSlaveCnt("PCS1");
          std::cout << "slaveCnt: " << slaveCnt << std::endl;
          std::cout << std::endl;
      
          // 获取设备具体信息
          std::cout << "获取设备具体信息" << std::endl;
          std::map<std::string, std::string> deviceInfo = client.GetDeviceInfo("PCS1");
          for (const auto &info : deviceInfo)
          {
              std::cout << info.first << ": " << info.second << std::endl;
          }
          std::cout << std::endl;
      
          // 获取从机表
          std::cout << "获取从机表" << std::endl;
          std::vector<std::vector<std::string>> table = client.GetDeviceTableBySlaveId("PCS1", 1, "");
          for (const auto &row : table)
          {
              for (const auto &col : row)
              {
                  std::cout << col << " ";
              }
              std::cout << std::endl;
          }
          std::cout << std::endl;
          return 0;
      }
      
    6. CMakeLists.txt

      set(TARGET device_rpc_client)
      
      # rpc客户端
      file(GLOB_RECURSE RPC_CLIENT_SOURCES "${CMAKE_SOURCE_DIR}/src/client/*.cpp" "${CMAKE_SOURCE_DIR}/src/proto/*.cc")
      
      # 查找 gRPC 和 Protobuf  
      find_package(Protobuf CONFIG REQUIRED)
      find_package(gRPC CONFIG REQUIRED)
      
      # 获取版本
      message(STATUS "Using protobuf-${Protobuf_VERSION}")
      message(STATUS "Using gRPC-${gRPC_VERSION}")
      
      # 包含 gRPC 和 Protobuf 的头文件  
      include_directories(${GRPC_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS})
      
      add_executable(${TARGET} ${RPC_CLIENT_SOURCES})
      
      target_include_directories(${TARGET} PUBLIC ${GTEST_INCLUDE_DIRS})
      target_link_libraries(${TARGET} PUBLIC gtest gtest_main)
      target_link_libraries(${TARGET} PUBLIC gRPC::grpc++_reflection protobuf::libprotobuf)
      
  5. grpc服务端和客户端通信效果展示

    在这里插入图片描述

  6. 样例源码仓库
    https://gitee.com/chen-dongyu123/grpc_example

总结

在C++中使用grpc还是有一定难度的,因为安装grpc的途中就伴有一大堆坑,其中重要的一点是千万不能使用国内镜像源下载,这个是大陷阱,很多依赖库版本对不上,最终会导致安装失败,挂VPN在Github上下载很快就能安装成功。后面我会继续讲一下c++和python使用grpc跨语言通讯的方法。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MC皮蛋侠客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值