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 调用的完整链路:
3.6 项目结构总览
本章完成后,项目结构应如下表所示:
| 层级 | 文件/目录 | 说明 |
|---|---|---|
| 定义层 | proto/helloworld.proto |
服务与消息的协议定义 |
| 生成层 | proto/helloworld.pb.go |
消息结构体(自动生成) |
| 生成层 | proto/helloworld_grpc.pb.go |
服务接口与注册函数(自动生成) |
| 实现层 | server/main.go |
服务端逻辑实现与服务器启动 |
| 构建层 | go.mod |
Go 模块依赖管理 |