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 调用流程

下图展示了从客户端发起请求到服务端返回响应的完整数据流:

Unary RPC 调用流程 客户端 SayHello() 调用 服务端 SayHello() 实现 HelloRequest{name} HelloReply{message} gRPC 框架层 序列化 / 传输 / 反序列化 Unary RPC 特点 1 次请求 → 1 次响应 | 同步阻塞模式 | 适合查询/写入类操作 超时控制通过 context.WithTimeout 实现

4.6 启动与测试

分步启动服务端与客户端,验证 Unary RPC 通信:

  1. 启动服务端:
cd server
go run main.go
# 输出: gRPC 服务端启动于 :50051
  1. 启动客户端(新终端):
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、拦截器、负载均衡等高级特性的基础。