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 内容 测试代办事项