59、gRPC:集成gRPC框架,编译.proto服务定义
说实话,gRPC 这玩意儿在微服务架构里已经快成标配了。我最早接触它是在一个跨语言通信的项目里,当时用 HTTP+JSON 传数据,解析慢、类型还容易丢。后来换成 gRPC,性能直接翻倍。嗯,今天我们就聊聊怎么在 CMake 里把它集成进来。
gRPC 到底解决了什么问题?
你想想看,传统的 RESTful 接口,客户端和服务端得约定好 URL、请求体、响应体。一旦接口变了,两边都得改。而且 JSON 序列化/反序列化在 C++ 里效率并不高。
gRPC 的思路是:先定义接口,再生成代码。你写一个 .proto 文件,里面描述好服务和方法。然后 protoc 编译器帮你生成客户端和服务端的 C++ 代码。这样接口就是「契约」,谁也别耍赖。
- 编写 .proto 文件(定义服务和消息)
- 用 protoc 编译 .proto → 生成 .pb.h / .pb.cc / .grpc.pb.h / .grpc.pb.cc
- 在 CMake 里把这些生成的文件加入构建
- 链接 gRPC 和 protobuf 的库
一个简单的 .proto 示例
先看个最基础的。假设我们要做一个「问候服务」——客户端传个名字,服务端回一句问候。
syntax = "proto3";
package helloworld;
// 定义请求消息
message HelloRequest {
string name = 1;
}
// 定义响应消息
message HelloReply {
string message = 1;
}
// 定义服务
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
这个文件保存为 helloworld.proto。注意 package 会影响生成的 C++ 命名空间。我个人习惯把 package 和目录结构保持一致,这样找文件方便。
CMake 里怎么集成?
这里有个坑——gRPC 的 CMake 集成方式有好几种。我踩过最深的坑是手动调用 protoc 命令,结果路径写错了,编译出来一堆空文件。后来我改用 CMake 的 find_package 配合自定义命令,稳定多了。
下面是我常用的模板:
cmake_minimum_required(VERSION 3.16)
project(grpc_demo LANGUAGES CXX)
# 找 protobuf 和 gRPC 的包
find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)
# 设置 .proto 文件路径
set(PROTO_FILE ${CMAKE_CURRENT_SOURCE_DIR}/helloworld.proto)
# 设置生成文件的输出目录
set(PROTO_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated)
# 自定义命令:编译 .proto
add_custom_command(
OUTPUT
${PROTO_GEN_DIR}/helloworld.pb.h
${PROTO_GEN_DIR}/helloworld.pb.cc
${PROTO_GEN_DIR}/helloworld.grpc.pb.h
${PROTO_GEN_DIR}/helloworld.grpc.pb.cc
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
--proto_path=${CMAKE_CURRENT_SOURCE_DIR}
--cpp_out=${PROTO_GEN_DIR}
--grpc_out=${PROTO_GEN_DIR}
--plugin=protoc-gen-grpc=${gRPC_CPP_PLUGIN_EXECUTABLE}
${PROTO_FILE}
DEPENDS ${PROTO_FILE}
COMMENT "Compiling helloworld.proto..."
)
# 把生成的文件打包成一个库
add_library(helloworld_proto
${PROTO_GEN_DIR}/helloworld.pb.cc
${PROTO_GEN_DIR}/helloworld.grpc.pb.cc
)
# 让编译器能找到生成的头文件
target_include_directories(helloworld_proto
PUBLIC ${PROTO_GEN_DIR}
)
# 链接必要的库
target_link_libraries(helloworld_proto
PUBLIC
protobuf::libprotobuf
gRPC::grpc++
gRPC::grpc++_reflection
)
find_package 找不到 gRPC,八成是安装路径没在 CMake 的搜索范围内。可以手动设置 -DgRPC_DIR=/path/to/grpc/cmake。
生成的文件结构
执行 CMake 后,generated 目录下会出现四个文件:
| 文件 | 作用 |
|---|---|
| helloworld.pb.h | 消息类的头文件(HelloRequest、HelloReply) |
| helloworld.pb.cc | 消息类的实现 |
| helloworld.grpc.pb.h | 服务类的头文件(Greeter 的客户端/服务端基类) |
| helloworld.grpc.pb.cc | 服务类的实现 |
说白了,.pb 文件管数据序列化,.grpc.pb 文件管 RPC 调用。两者缺一不可。
编写服务端和客户端
有了生成的文件,写服务端就很简单了。继承 Greeter::Service,重写 SayHello 方法:
#include <grpcpp/grpcpp.h>
#include "helloworld.grpc.pb.h"
class GreeterServiceImpl final : public helloworld::Greeter::Service {
grpc::Status SayHello(
grpc::ServerContext* context,
const helloworld::HelloRequest* request,
helloworld::HelloReply* reply
) override {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return grpc::Status::OK;
}
};
int main() {
GreeterServiceImpl service;
grpc::ServerBuilder builder;
builder.AddListeningPort("0.0.0.0:50051", grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
server->Wait();
return 0;
}
客户端更直接:
#include <grpcpp/grpcpp.h>
#include "helloworld.grpc.pb.h"
int main() {
auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());
auto stub = helloworld::Greeter::NewStub(channel);
helloworld::HelloRequest request;
request.set_name("World");
helloworld::HelloReply reply;
grpc::ClientContext context;
grpc::Status status = stub->SayHello(&context, request, &reply);
if (status.ok()) {
std::cout << reply.message() << std::endl;
}
return 0;
}
InsecureServerCredentials。我曾经在测试环境忘了加 SSL,结果被安全扫描工具报了个高危漏洞。gRPC 支持 TLS,建议用 SslServerCredentials。
集成到 CMake 的完整示例
把服务端和客户端也加进 CMakeLists.txt:
add_executable(server server.cpp)
target_link_libraries(server PRIVATE helloworld_proto)
add_executable(client client.cpp)
target_link_libraries(client PRIVATE helloworld_proto)
这样整个项目就完整了。先启动 server,再运行 client,就能看到输出 Hello World。
gRPC 集成流程总览
下面这张图帮你理清整个流程:
避坑指南
我总结几个常见问题:
- protoc 版本不匹配: gRPC 和 protobuf 的版本要对应。我曾经把 protobuf 3.20 和 gRPC 1.45 混用,结果编译报了一堆模板错误。建议用 vcpkg 或 conan 统一管理版本。
- 找不到 gRPC 插件:
protoc-gen-grpc这个可执行文件必须存在。如果你是从源码编译的 gRPC,它在build/grpc_cpp_plugin里。 - 链接顺序: gRPC 依赖 protobuf,链接时要把 gRPC 放前面。CMake 的
target_link_libraries会自动处理,但如果你手动写target_link_libraries顺序错了,会报 undefined reference。
好了,gRPC 集成的基本套路就这些。说白了就是三步:写 .proto、编译、链接。只要把 CMake 的自定义命令写对,后面就顺了。
公众号:蓝海资料掘金营,微信deep3321