3. gRPC 服务定义:从 Proto 到 Unary RPC 实现

本章将完整走通 gRPC 服务的定义与实现链路:编写 helloworld.proto 文件、使用 protoc 生成 Go 代码、实现 Unary RPC 服务端逻辑,并启动 gRPC 服务器。这是后续所有安全验证与压测的基础骨架。

3.1 定义 helloworld.proto

在项目根目录下创建 proto/helloworld.proto,定义服务接口与消息结构。使用 proto3 语法,声明 Go 包路径以便代码生成。

syntax = "proto3";

package helloworld;

option go_package = "github.com/your-project/helloworld/proto/helloworld";

// Greeter 服务:包含一个 Unary RPC
service Greeter {
  // SayHello:接收 HelloRequest,返回 HelloReply
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// 请求消息:包含客户端名称
message HelloRequest {
  string name = 1;
}

// 响应消息:包含服务端问候语
message HelloReply {
  string message = 1;
}
关键点: go_package 必须与项目模块路径一致,否则生成的 Go 代码导入路径会出错。建议使用 module/proto/package 的命名规范。

3.2 生成 Go 代码

使用 protoc 编译器配合 Go 插件生成服务端与客户端桩代码。确保已安装以下工具:

工具 安装命令 用途
protoc 系统包管理器安装 协议缓冲区编译器
protoc-gen-go go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 生成消息类型的 Go 结构体
protoc-gen-go-grpc go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 生成 gRPC 服务端/客户端接口

在项目根目录执行以下命令生成代码:

protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       proto/helloworld.proto

生成后项目结构如下:

project-root/
├── proto/
│   ├── helloworld.proto          # 原始定义
│   ├── helloworld.pb.go          # 消息结构体
│   └── helloworld_grpc.pb.go     # 服务接口与注册函数
├── server/
│   └── main.go                   # 服务端实现(待编写)
└── go.mod
生成文件说明:
  • helloworld.pb.go:包含 HelloRequest、HelloReply 结构体及其序列化方法。
  • helloworld_grpc.pb.go:包含 GreeterServer 接口、RegisterGreeterServer 注册函数、以及客户端桩代码。

3.3 实现 Unary RPC 服务端

在 server/main.go 中实现 GreeterServer 接口。Unary RPC 是最简单的 RPC 模式:客户端发送一个请求,服务端返回一个响应。

package main

import (
    "context"
    "fmt"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "github.com/your-project/helloworld/proto/helloworld"
)

// server 结构体实现 GreeterServer 接口
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello 实现 Unary RPC 方法
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
    // 构造响应消息
    message := fmt.Sprintf("Hello, %s! Welcome to gRPC Security Course.", req.GetName())
    return &pb.HelloReply{Message: message}, nil
}

func main() {
    // 监听 TCP 端口
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    // 创建 gRPC 服务器实例
    s := grpc.NewServer()

    // 注册服务实现
    pb.RegisterGreeterServer(s, &server{})

    log.Printf("gRPC server listening on :50051")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
注意: 必须嵌入 pb.UnimplementedGreeterServer,这样即使未来 proto 新增 RPC 方法,现有代码也能编译通过,避免接口不兼容错误。

3.4 启动 gRPC 服务器

运行服务端:

go run server/main.go

输出日志:

2025/01/15 10:30:00 gRPC server listening on :50051

此时服务器已启动,等待客户端连接。可以使用 grpcurl 工具快速验证:

grpcurl -plaintext -d '{"name": "Alice"}' localhost:50051 helloworld.Greeter/SayHello

预期响应:

{
  "message": "Hello, Alice! Welcome to gRPC Security Course."
}

3.5 完整调用流程 SVG

下图展示了从 Proto 定义到 Unary RPC 调用的完整链路:

Unary RPC 调用流程 Unary RPC 调用流程 helloworld.proto protoc 生成 Go 代码 server/main.go gRPC Server :50051 客户端 SayHello SayHello 方法 HelloReply 响应 返回响应 Unary RPC 是最简单的 RPC 模式:1 请求 → 1 响应,适合查询、认证、简单计算等场景。

3.6 项目结构总览

本章完成后,项目结构应如下表所示:

层级 文件/目录 说明
定义层 proto/helloworld.proto 服务与消息的协议定义
生成层 proto/helloworld.pb.go 消息结构体(自动生成)
生成层 proto/helloworld_grpc.pb.go 服务接口与注册函数(自动生成)
实现层 server/main.go 服务端逻辑实现与服务器启动
构建层 go.mod Go 模块依赖管理