4. 第一个gRPC服务:定义proto文件、生成Go代码、实现服务端、实现客户端、运行测试
本章是gRPC开发的“Hello World”实战环节。你将亲手完成从契约定义到代码运行的全流程:编写.proto文件、使用protoc工具生成Go桩代码、实现服务端与客户端逻辑,并验证双向通信。这是后续所有审计与安全加固的基础。
4.1 整体流程概览
一个完整的gRPC服务开发遵循“契约优先”原则,流程分为五个步骤:
- 定义契约:编写
.proto文件,声明服务接口与消息结构。 - 生成代码:使用
protoc编译器生成Go语言的客户端桩代码和服务端接口。 - 实现服务端:编写Go结构体,实现生成的接口方法。
- 实现客户端:使用生成的客户端桩代码发起RPC调用。
- 运行测试:启动服务端,运行客户端,验证通信是否正常。
关键原则:.proto文件是团队协作的“单一事实来源”。服务端与客户端必须基于同一份proto文件生成代码,否则契约断裂将导致通信失败。
4.2 定义proto文件
创建一个名为hello.proto的文件,定义Greeter服务及其消息类型。这是整个系统的契约基础。
syntax = "proto3";
package hello;
option go_package = "github.com/example/hello/proto/hello";
// Greeter 服务:提供问候功能
service Greeter {
// 一元RPC:发送一个请求,接收一个响应
rpc SayHello (HelloRequest) returns (HelloReply);
}
// 请求消息:包含用户名
message HelloRequest {
string name = 1;
}
// 响应消息:包含问候语
message HelloReply {
string message = 1;
}
字段编号规则:消息中的每个字段必须有一个唯一的编号(1-15占用1字节,16-2047占用2字节)。频繁使用的字段应使用小编号以节省带宽。
4.3 生成Go代码
使用protoc编译器生成Go桩代码。确保已安装protoc、protoc-gen-go和protoc-gen-go-grpc插件。
# 安装protoc-gen-go和protoc-gen-go-grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 生成代码
protoc --go_out=. --go-grpc_out=. hello.proto
执行后生成两个文件:
| 生成文件 | 内容说明 |
|---|---|
hello.pb.go |
消息类型的Go结构体(HelloRequest、HelloReply)及序列化/反序列化方法 |
hello_grpc.pb.go |
服务端接口(GreeterServer)、客户端桩代码(GreeterClient)及注册函数 |
审计关注点:生成的
hello_grpc.pb.go中定义了GreeterServer接口。任何服务端实现都必须完整实现该接口的所有方法,否则编译会报错。这是契约强制性的体现。
4.4 实现服务端
创建server/main.go,实现GreeterServer接口:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/example/hello/proto/hello"
)
// server 结构体实现 GreeterServer 接口
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("服务端启动,监听 :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("服务启动失败: %v", err)
}
}
安全基线:生产环境中
grpc.NewServer()默认不启用TLS。必须显式配置grpc.Creds()选项启用TLS加密,否则所有通信将以明文传输,违反安全审计要求。
4.5 实现客户端
创建client/main.go,使用生成的客户端桩代码发起RPC调用:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "github.com/example/hello/proto/hello"
)
func main() {
// 连接服务端(不启用TLS,仅用于演示)
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer conn.Close()
client := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 调用 SayHello
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "gRPC"})
if err != nil {
log.Fatalf("调用失败: %v", err)
}
log.Printf("收到响应: %v", resp.GetMessage())
}
4.6 运行测试
启动服务端和客户端,验证通信是否正常:
# 终端1:启动服务端
go run server/main.go
# 输出: 服务端启动,监听 :50051
# 终端2:运行客户端
go run client/main.go
# 输出: 收到响应: Hello gRPC
4.7 常见问题与排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
protoc: command not found |
未安装protoc或未加入PATH | 从protobuf releases下载并安装,确保protoc可执行 |
undefined: pb.RegisterGreeterServer |
未正确导入生成的pb包 | 检查go_package路径与import路径是否一致 |
connection refused |
服务端未启动或端口错误 | 确认服务端已运行且监听端口与客户端一致 |
rpc error: code = Unimplemented |
服务端未注册服务或方法未实现 | 检查RegisterGreeterServer调用及接口方法实现 |
调试技巧:使用
grpcurl工具可以无需编写客户端代码即可测试gRPC服务:grpcurl -plaintext localhost:50051 list 查看已注册的服务列表。
4.8 本章小结
通过本章实战,你完成了第一个gRPC服务的完整开发流程:
- 编写
.proto文件定义服务契约 - 使用
protoc生成Go桩代码 - 实现服务端接口并启动gRPC服务
- 实现客户端并成功调用远程方法
- 验证了契约一致性对通信成功的关键作用