Protobuf文件必须首行声明syntax="proto3",gRPC C++仅支持proto3;字段默认optional,枚举首值须为0;需统一protoc版本生成代码,服务端用BuildAndStart()启动,客户端高并发应使用异步API与复用CompletionQueue。
syntax = "proto3"
很多初学者在写 .proto 文件时漏掉语法声明,导致 protoc 编译失败并报错 Expected "syntax = ..." 。gRPC C++ 默认只支持 proto3,且不兼容 proto2 的默认值、required 字段等行为。
syntax = "proto3"; 必须是文件第一行(可空行,但不可注释)optional 关键字(加了会报错)google/protobuf/wrappers.proto 中的 Int32Value 等才能表达“空值”语义syntax = "proto3";
package example;
message Request {
string user_id = 1;
int32 timeout_ms = 2;
}
message Response {
bool success = 1;
string message = 2;
}
service Greeter {
rpc SayHello(Request) returns (Response);
}
protoc 生成相同的头文件和源文件手动改写或复用旧版生成代码极易引发 ABI 不匹配:比如服务端用 protobuf 3.21 编译,客户端用 3.19 生成的 *.grpc.pb.h,会导致 Unknown field in serialization 或崩溃。
protoc(推荐与 libprotobuf-dev 同版本)执行生成--grpc_out 和 --cpp_out,且顺序不能颠倒libgrpc++、libprotobuf、libgrpc —— 少一个就链接失败或运行时 undefined symbol
#include 路径包含生成目录,且优先级高于系统 protobuf 头(避免混用)ServerBuilder::BuildAndStart(),不能只调用 Build()
常见错误是只调用 builder.Build(),结果程序无报错但监听端口不生效、客户端连接超时。这是因为 Build() 只返回 std::unique_ptr,但没真正启动 I/O 循环。
BuildAndStart() 内部会启动 gRPC 的 completion queue 线程池,默认线程数为 CPU 核心数SetMaxMessageSize()、SetResourceQuota() 限制,而非减少线程数server->Shutdown(),再 wait_for_idle(),否则可能丢弃正在处理的请求grpc::ServerBuilder builder;
builder.AddListeningPort("0.0.0.0:50051", grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr server = builder.BuildAndStart(); // 注意是 BuildAndStart
std::cout << "Server listening on 0.0.0.0:50051\n";
server->Wait();
CompletionQueue
用 stub->SayHello(&context, request, &response) 这种同步方式,在 QPS > 100 时容易因网络延迟堆积线程,最终耗尽内存或触发 OS 线程创建失败。
CompletionQueue::Next() 统一收包CompletionQueue;推荐复用 1–4 个全局队列,按负载绑定线程ClientContext 生命周期:必须在 AsyncNext() 返回前保持有效,否则回调中访问 context 成员会 crashClientContext::set_deadline(),而不是靠外部 timer —— gRPC 内部 deadline 检查更精确