跳到主要内容

GRPC 实现 TodoList

环境准备

在开始前我已准备好开发环境,本示例使用 Go 语言和 GRPC 版本 1.54.0。已安装 Go 编程语言并正确配置 GOPATH 环境变量。安装 protoc 编译器及 protoc-gen-go 和 protoc-gen-go-grpc 插件,并将它们的安装目录加入 PATH 以便直接使用 protoc 命令。使用 WithInsecure 选项时网络通信不加密,只在本地开发测试时使用。

定义 Proto 文件

下面为 todo.proto 文件的内容

syntax = "proto3";

package todo;

service TodoService {
rpc AddTodo (TodoItem) returns (AddTodoResponse);
rpc ListTodos (ListTodosRequest) returns (TodoListResponse);
}

message TodoItem {
int32 id = 1;
string content = 2;
}

message AddTodoResponse {
bool success = 1;
}

message ListTodosRequest {}

message TodoListResponse {
repeated TodoItem items = 1;
}

生成 GRPC 代码

通过 protoc 命令生成对应的 Go 代码

protoc --go_out=. --go-grpc_out=. todo.proto

实现服务端

在 server.go 文件中实现服务端逻辑。下面的实现使用内存作为数据存储示例。在实际生产环境中可以将数据持久化到数据库,并在需要时增加鉴权与加密。启动服务前可以考虑使用 context 或其他方式实现优雅关闭。

package main

import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"

"google.golang.org/grpc"
"todo"
)

type todoServiceServerImpl struct {
todo.UnimplementedTodoServiceServer
todoItemsList []*todo.TodoItem
}

func (s *todoServiceServerImpl) AddTodo(ctx context.Context, item *todo.TodoItem) (*todo.AddTodoResponse, error) {
s.todoItemsList = append(s.todoItemsList, item)
return &todo.AddTodoResponse{Success: true}, nil
}

func (s *todoServiceServerImpl) ListTodos(ctx context.Context, req *todo.ListTodosRequest) (*todo.TodoListResponse, error) {
return &todo.TodoListResponse{Items: s.todoItemsList}, nil
}

func main() {
serverListener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("监听失败 %v", err)
}
grpcServerInstance := grpc.NewServer()
todo.RegisterTodoServiceServer(grpcServerInstance, &todoServiceServerImpl{})

signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

go func() {
if err := grpcServerInstance.Serve(serverListener); err != nil {
log.Fatalf("服务启动失败 %v", err)
}
}()

log.Println("服务端已启动并监听50051")

<-signals
grpcServerInstance.GracefulStop()
log.Println("服务端已优雅退出")
}

实现客户端

在 client.go 文件中实现客户端逻辑,演示添加条目并列出所有条目。在实际中可将这些调用封装成函数提供给上层业务逻辑使用。

package main

import (
"context"
"log"
"time"

"google.golang.org/grpc"
"todo"
)

func main() {
serverConnection, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("连接失败 %v", err)
}
defer serverConnection.Close()

todoClientInstance := todo.NewTodoServiceClient(serverConnection)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

_, err = todoClientInstance.AddTodo(ctx, &todo.TodoItem{Id: 1, Content: "测试代办事项"})
if err != nil {
log.Fatalf("添加失败 %v", err)
}

listResp, err := todoClientInstance.ListTodos(ctx, &todo.ListTodosRequest{})
if err != nil {
log.Fatalf("获取列表失败 %v", err)
}

for _, todoItem := range listResp.Items {
log.Printf("Todo ID %d 内容 %s", todoItem.Id, todoItem.Content)
}
}

测试

在一个终端中运行服务端

go run server.go

在另一个终端运行客户端

go run client.go

预期输出为

2023/10/10 12:00:00 Todo ID 1 内容 测试代办事项