3. Go gRPC 快速入门:protoc 工具链安装、生成 Go 代码、实现简单的 Unary RPC 服务端与客户端

本章是 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 运行与验证

  1. 启动服务端:go run server/main.go,输出 gRPC 服务端启动于 :50051
  2. 启动客户端:go run client/main.go,输出 服务端响应: Hello gRPC 工程师
✅ 验证成功标志: 客户端收到服务端返回的问候语,且服务端打印了请求日志。说明 protoc 工具链、桩代码生成、Unary RPC 通信链路全部正常。

3.7 Unary RPC 调用流程(SVG)

下图展示了一次完整的 Unary RPC 调用过程,从客户端发起请求到服务端返回响应:

Unary RPC 调用流程 Unary RPC 调用流程 客户端 GreeterClient SayHello() 序列化请求 等待响应 服务端 GreeterServer 接收请求 反序列化 SayHello 业务逻辑 HelloRequest (protobuf 二进制) HelloReply (protobuf 二进制) 一次请求 + 一次响应 = 一元 RPC(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 实现:服务端实现接口,客户端通过桩代码调用