3. Go gRPC快速入门
本章是 gRPC 实战的起点。我们将从零搭建 protoc 工具链,生成 Go 代码,并实现一个完整的 Unary RPC(一元 RPC)服务端与客户端。通过本章,你将掌握 gRPC 开发的核心流程:定义契约 → 生成桩代码 → 实现业务逻辑 → 启动服务与调用。
3.1 工具链安装与验证
gRPC 依赖 protoc(Protocol Buffers 编译器)及其 Go 插件。请按以下步骤安装并验证。
| 组件 | 安装方式 | 验证命令 |
|---|---|---|
| protoc | 从 GitHub Releases 下载对应平台的二进制,解压后加入 PATH |
protoc --version 输出 libprotoc 3.x.x |
| protoc-gen-go | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest |
protoc-gen-go --version 输出版本号 |
| protoc-gen-go-grpc | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest |
protoc-gen-go-grpc --version 输出版本号 |
💡 提示: 确保
$GOPATH/bin 已加入 PATH,否则 protoc 无法找到插件。执行 go env GOPATH 查看路径。
3.2 定义 Proto 契约文件
在项目根目录创建 proto/hello.proto,定义服务接口与消息结构:
syntax = "proto3";
package hello;
option go_package = "github.com/yourname/grpc-demo/proto/hello";
// 请求消息:包含名称
message HelloRequest {
string name = 1;
}
// 响应消息:包含问候语
message HelloReply {
string message = 1;
}
// 服务定义:一元 RPC
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
⚠️ 注意:
go_package 必须与 Go 模块路径一致,否则生成的 import 路径会出错。建议使用 module/proto/包名 的格式。
3.3 生成 Go 桩代码
在项目根目录执行以下命令:
protoc --go_out=. --go-grpc_out=. proto/hello.proto
执行后,proto/hello/ 目录下会生成两个文件:
hello.pb.go:消息结构的 Go 代码(序列化/反序列化)hello_grpc.pb.go:服务端接口与客户端桩代码
生成的桩代码结构如下:
// 服务端接口(需实现)
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
// 客户端桩代码(直接调用)
type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
3.4 实现 Unary RPC 服务端
创建 server/main.go,实现 GreeterServer 接口:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/yourname/grpc-demo/proto/hello"
)
// 服务端结构体
type server struct {
pb.UnimplementedGreeterServer
}
// 实现 SayHello 方法
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("收到请求: %v", req.GetName())
return &pb.HelloReply{Message: "Hello " + req.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Println("gRPC 服务端启动于 :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("服务启动失败: %v", err)
}
}
3.5 实现 Unary RPC 客户端
创建 client/main.go,通过桩代码发起调用:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/yourname/grpc-demo/proto/hello"
)
func main() {
// 建立连接(开发环境使用不安全连接)
conn, err := grpc.Dial("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer conn.Close()
client := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 发起一元 RPC 调用
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "gRPC 工程师"})
if err != nil {
log.Fatalf("调用失败: %v", err)
}
log.Printf("服务端响应: %s", resp.GetMessage())
}
3.6 运行与验证
- 启动服务端:
go run server/main.go,输出gRPC 服务端启动于 :50051 - 启动客户端:
go run client/main.go,输出服务端响应: Hello gRPC 工程师
✅ 验证成功标志: 客户端收到服务端返回的问候语,且服务端打印了请求日志。说明 protoc 工具链、桩代码生成、Unary RPC 通信链路全部正常。
3.7 Unary RPC 调用流程(SVG)
下图展示了一次完整的 Unary RPC 调用过程,从客户端发起请求到服务端返回响应:
3.8 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
protoc: command not found |
protoc 未安装或未加入 PATH | 下载 protoc 二进制并解压到 /usr/local/bin |
protoc-gen-go: program not found |
Go 插件未安装或 $GOPATH/bin 不在 PATH |
执行 go install 并检查 PATH |
undefined: pb.RegisterGreeterServer |
未正确导入生成的桩代码包 | 检查 go_package 路径与 import 是否一致 |
connection refused |
服务端未启动或端口错误 | 确认服务端监听端口,客户端连接地址一致 |
rpc error: code = Unavailable |
服务端崩溃或网络不通 | 检查服务端日志,使用 grpcurl 探测 |
3.9 本章小结
通过本章,你已掌握:
- protoc 工具链安装:protoc + protoc-gen-go + protoc-gen-go-grpc
- Proto 契约定义:消息结构 + 服务接口
- 桩代码生成:
protoc --go_out --go-grpc_out - Unary RPC 实现:服务端实现接口,客户端通过桩代码调用