Protocol Buffer 接口必须显式定义 service 块,否则 protoc 不生成服务端结构体或客户端 stub;gRPC 严格依赖 service 定义,rpc 方法参数和返回值须为 message 类型,且需调用 RegisterXXXServer 注册实现。

定义 Protocol Buffer 接口时必须显式声明 service 和 rpc 方法
很多初学者写完 .proto 文件后,protoc 生成 Go 代码却没出现服务端结构体或客户端 stub,根本原因是漏写了 service 块。gRPC 不是靠 message 自动导出 RPC,而是严格依赖 service 定义。
正确示例:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
-
service名称会映射为 Go 中的接口名(如GreeterServer) - 每个
rpc方法对应一个函数签名,参数和返回值必须是已定义的message - 不支持直接返回基本类型(如
string或int32),必须包在message里 - 若用
protoc-gen-go-grpc(新版插件),还需额外加--go-grpc_out=.参数,旧版protoc-gen-go默认不生成 server/client 接口
grpc.Server 启动前必须注册服务实现,且不能重复注册同名 service
启动 gRPC 服务时,grpc.NewServer() 创建实例后,必须调用 RegisterXXXServer()(由 protoc 生成)传入具体实现。否则客户端连接成功但调用任何方法都会返回 UNIMPLEMENTED 错误。
常见错误现象:rpc error: code = Unimplemented desc = Method not found
立即学习“go语言免费学习笔记(深入)”;
- 确保调用的是生成代码里的
RegisterGreeterServer(s, &server{}),不是手写的、拼错的或大小写不一致的函数名 - 同一个
grpc.Server实例上不能多次注册同一 service(比如两次RegisterGreeterServer),会 panic 报duplicate registration - 如果用了拦截器(interceptor),需通过
grpc.UnaryInterceptor或grpc.StreamInterceptor选项传入,不能在Register后再“补加” - 监听地址建议用
net.Listen("tcp", ":50051"),避免硬编码 IP;生产环境应设超时、Keepalive、最大连接数等参数
客户端连接需手动管理 *grpc.ClientConn 生命周期
Go 的 gRPC 客户端不是“即用即连”,grpc.Dial() 返回的 *grpc.ClientConn 是长连接资源,必须显式关闭,否则导致 fd 泄露、DNS 缓存失效、连接堆积等问题。
- 务必在使用完毕后调用
conn.Close(),推荐用defer conn.Close()(注意作用域) - 不要为每次 RPC 调用都新建
grpc.Dial()—— 开销大且易触发连接风暴;应复用*grpc.ClientConn - 若服务端地址是域名,需开启 DNS 解析:加
grpc.WithTransportCredentials(insecure.NewCredentials())(开发)或grpc.WithTransportCredentials(credentials.NewTLS(...))(生产),并配置grpc.WithResolvers(...)才能自动刷新 - 连接失败时,
grpc.Dial()默认阻塞直到成功或超时(默认 10s),可用grpc.WithBlock()控制,但更推荐用grpc.FailOnNonTempDialError(true)+ context 控制重试逻辑
流式 RPC 的 Send/Recv 必须配对且注意 EOF 判断
对于 stream 类型的 RPC(如 rpc StreamingCall(stream Request) returns (stream Response)),客户端和服务端的读写顺序、EOF 处理极易出错,典型表现是 goroutine 永久阻塞或提前退出。
- 客户端发送完所有请求后,必须调用
stream.CloseSend(),否则服务端的Recv()永远不会返回io.EOF - 服务端响应流中,每次
stream.Send()后,客户端stream.Recv()可能返回nil(成功)或非nil错误;只有当错误是io.EOF时才表示流结束 - 不要在循环里无条件
Recv(),需检查错误类型:if err == io.EOF { break },否则可能 panic - 流式调用不支持超时透传到单次
Send/Recv,需在每次调用时单独套context.WithTimeout
ClientConn 的复用策略和流式调用中 CloseSend() 的调用时机——这两个点线上出问题时,日志往往只显示 “context deadline exceeded” 或 “broken pipe”,实际根因却藏在连接生命周期管理里。










