C++使用grpc详例(使用CMake构建)
本文讲解了在C++使用grpc传递基本数据类型、一维数组、二维数组、自定义结构体类型的基本方法,总体来讲,在C++中使用grpc还是有一定难度的,因为安装grpc的途中就伴有一大堆坑,其中重要的一点是千万不能使用国内镜像源下载,这个是大陷阱,很多依赖库版本对不上,最终会导致安装失败,挂VPN在Github上下载很快就能安装成功。后面我会继续讲解c++和python使用grpc跨语言通讯的方法。
安装grpc(Ubuntu)
-
安装protobuf编译器和库
sudo apt update sudo apt install -y protobuf-compiler libprotobuf-dev
-
安装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
-
编写.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; }
-
根据.proto文件,生成服务端和客户端代码
生成客户端代码
protoc --cpp_out=. device.proto
生成服务端代码
protoc --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` device.proto
-
编写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文件
-
检查设备是否存在
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; }
-
返回一维字符串列表
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; }
-
返回单个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; } }
-
返回自定义结构体类型
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; } }
-
返回二维字符串列表
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; } }
-
服务端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; }
-
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)
-
-
编写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文件
-
获取一维字符串列表
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 {}; } }
-
获取单个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; } }
-
获取自定义结构体信息
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 {}; } }
-
获取二维字符串列表
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 {}; } }
-
客户端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; }
-
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)
-
-
grpc服务端和客户端通信效果展示
总结
在C++中使用grpc还是有一定难度的,因为安装grpc的途中就伴有一大堆坑,其中重要的一点是千万不能使用国内镜像源下载,这个是大陷阱,很多依赖库版本对不上,最终会导致安装失败,挂VPN在Github上下载很快就能安装成功。后面我会继续讲一下c++和python使用grpc跨语言通讯的方法。
更多推荐
所有评论(0)