Protocol Buffer 接口必须在 .proto 文件中正确定义,包括 syntax、package、service 和 message;需用 protoc-gen-go 与 protoc-gen-go-grpc 插件生成代码;Server 必须调用 RegisterXXXServer 并监听;Client 需复用连接并设置 context 超时。

定义 Protocol Buffer 接口(.proto 文件)
gRPC 的核心是接口定义必须先写在 .proto 文件里,Go 代码只是生成结果。不写对这个文件,后续所有步骤都会失败。
常见错误:直接写 Go 结构体当服务接口、漏写 syntax = "proto3";、在 service 中用 rpc 声明方法但没配对的 message 请求/响应类型。
- 必须声明
package,它会映射为 Go 的包名(如package helloworld;→ 生成代码在helloworld目录) - 服务方法参数和返回值必须是
message类型,不能是基础类型(如string或int32单独作参数) - 若需流式通信,用
stream关键字,例如rpc SayHelloStream(stream HelloRequest) returns (stream HelloReply);
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
用 protoc 生成 Go 代码
Go 不自带 protoc 插件,必须手动安装 protoc-gen-go 和 protoc-gen-go-grpc,否则 protoc 会报错“Plugin failed with status code 1”。
生成命令容易漏掉 --go-grpc_out —— 只用 --go_out 只生成数据结构,不生成 gRPC Server/Client 接口,运行时会提示 undefined: helloworld.NewGreeterClient 这类错误。
立即学习“go语言免费学习笔记(深入)”;
- 安装插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest和go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest - 执行生成(确保当前目录有
helloworld.proto):protoc --go_out=. --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld.proto
- 生成后会得到两个文件:
helloworld/helloworld.pb.go(消息结构)和helloworld/helloworld_grpc.pb.go(客户端接口、服务端抽象接口)
实现 gRPC Server(基于 net.Listener)
Server 启动依赖 net.Listen + grpc.NewServer() + RegisterXXXServer 三步缺一不可。最容易忽略的是没调用 RegisterXXXServer,导致客户端调用时返回 UNIMPLEMENTED 错误。
另一个坑是未处理监听地址冲突(如端口被占),net.Listen 失败后没检查 error,程序静默 panic 或卡住。
-
grpc.NewServer()可传入选项,例如启用 TLS:grpc.Creds(credentials.NewTLS(...)) - 注册服务必须用生成的
RegisterGreeterServer函数,不是手写的结构体指针直传 - 启动后需阻塞等待连接,常用
fmt.Scanln()或signal.Notify捕获中断
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "your-module/helloworld"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + req.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
编写 gRPC Client 并处理连接生命周期
Client 端最常出问题的是连接没关闭、没设超时、或复用已关闭的 *grpc.ClientConn。一旦连接断开又继续调用,会报 rpc error: code = Unavailable desc = closing transport due to: connection error。
不要把 grpc.Dial 写在每次 RPC 调用前——它开销大且可能泄漏连接;也不要在 defer 中无条件 Close(),因为连接通常要复用。
- 推荐用
grpc.WithTransportCredentials(insecure.NewCredentials())开发时跳过 TLS(生产环境必须配证书) - 务必设置上下文超时:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second),再传给 RPC 方法 - 连接应作为长生命周期对象管理(如封装进 struct),避免频繁 Dial/Close
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "your-module/helloworld"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := client.SayHello(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
实际跑通的关键不在语法多炫,而在 .proto 定义是否闭环、protoc 插件是否齐全、Server 是否完成 Register、Client 是否正确携带 context 超时——这四点任一缺失,都会让请求卡在无声的失败里。










