4. Unary RPC 实现:定义 proto 文件、生成 Go 代码、实现服务端接口、实现客户端调用、启动与测试
Unary RPC(一元 RPC)是 gRPC 中最基础的通信模式:客户端发送一个请求,服务端返回一个响应。本章将完整演示从 .proto 定义到 Go 代码生成、服务端实现、客户端调用,再到启动与测试的全流程闭环。
4.1 定义 proto 文件
在项目根目录下创建 proto/hello.proto,定义服务接口与消息结构:
syntax = "proto3";
package hello;
option go_package = "github.com/your-project/hello/proto/hello";
// 定义 Unary RPC 服务
service Greeter {
// SayHello 方法:接收 HelloRequest,返回 HelloReply
rpc SayHello (HelloRequest) returns (HelloReply);
}
// 请求消息:包含用户名
message HelloRequest {
string name = 1;
}
// 响应消息:包含问候语
message HelloReply {
string message = 1;
}
字段编号规则:每个字段必须分配唯一的编号(1-15 占用 1 字节,16-2047 占用 2 字节)。频繁使用的字段优先使用小编号。
4.2 生成 Go 代码
使用 protoc 编译器配合 Go 插件生成服务端与客户端桩代码:
# 安装 protoc 与 Go 插件(如未安装)
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=. proto/hello.proto
生成的文件结构:
| 文件 | 内容 |
|---|---|
proto/hello/hello.pb.go |
消息结构体(HelloRequest、HelloReply)的序列化/反序列化代码 |
proto/hello/hello_grpc.pb.go |
服务端接口(GreeterServer)、客户端桩(GreeterClient)及注册函数 |
路径对齐:确保
go_package 与项目模块路径一致,否则生成的 import 路径会出错。
4.3 实现服务端接口
创建 server/main.go,实现 GreeterServer 接口:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/your-project/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("收到请求: name=%s", req.Name)
return &pb.HelloReply{
Message: "Hello, " + req.Name + "!",
}, 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)
}
}
4.4 实现客户端调用
创建 client/main.go,建立连接并调用 Unary RPC:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/your-project/hello/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()
// 发起 Unary RPC 调用
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "gRPC 专家"})
if err != nil {
log.Fatalf("调用失败: %v", err)
}
log.Printf("响应消息: %s", resp.Message)
}
4.5 Unary RPC 调用流程
下图展示了从客户端发起请求到服务端返回响应的完整数据流:
4.6 启动与测试
分步启动服务端与客户端,验证 Unary RPC 通信:
- 启动服务端:
cd server
go run main.go
# 输出: gRPC 服务端启动于 :50051
- 启动客户端(新终端):
cd client
go run main.go
# 输出: 响应消息: Hello, gRPC 专家!
4.7 项目结构总览
完成上述步骤后,项目目录结构如下:
hello/
├── proto/
│ ├── hello.proto # 原始 proto 定义
│ └── hello/ # 生成的 Go 代码
│ ├── hello.pb.go
│ └── hello_grpc.pb.go
├── server/
│ └── main.go # 服务端实现
├── client/
│ └── main.go # 客户端实现
├── go.mod
└── go.sum
验证要点:
- 服务端日志输出
收到请求: name=gRPC 专家 - 客户端日志输出
响应消息: Hello, gRPC 专家! - 使用
grpcurl -plaintext localhost:50051 list可查看注册的服务列表
4.8 常见问题与排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
protoc-gen-go-grpc: program not found |
未安装或 PATH 未包含 Go 二进制目录 | 执行 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest |
connection refused |
服务端未启动或端口被占用 | 检查服务端进程,使用 lsof -i :50051 查看端口 |
rpc error: code = DeadlineExceeded |
客户端超时设置过短或服务端处理过慢 | 增大 context.WithTimeout 的时间值 |
Unimplemented 错误 |
服务端未注册 GreeterServer | 确认调用了 pb.RegisterGreeterServer(s, &server{}) |
至此,Unary RPC 的完整闭环已搭建完成。该模式是后续流式 RPC、拦截器、负载均衡等高级特性的基础。