第五节——实现一元GRPC

Posted 想学习安全的小白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第五节——实现一元GRPC相关的知识,希望对你有一定的参考价值。

第五章——使用一元GRPC传递pcbook

5.1、定义一个 proto 服务和一个一元 RPC

  1. 创建新文件——proto/laptop_service.proto
  2. 定义一条CreateLaptopRequest消息,它只包含一个字段:我们要创建的笔记本电脑
message CreateLaptopRequest 
  Laptop laptop = 1;

  1. 定义CreateLaptopResponse消息也只有 1 个字段:创建的笔记本电脑的 ID
message CreateLaptopResponse 
  string id = 1;

  1. 定义一元GRPC
service LaptopService 
  rpc CreateLaptop(CreateLaptopRequest) returns (CreateLaptopResponse) ;

5.2、配置服务端

  1. 创建service文件夹,创建service/laptop_server.go文件
  2. 声明一个LaptopServer结构和一个NewLaptopServer()函数来返回它的一个新实例
type LaptopServer struct 
	pb.UnimplementedLaptopServiceServer


func NewLaptopServer() *LaptopServer 
	return &LaptopServer

  1. laptop_service.proto中定义了一个CreateLaptop一元服务,在此go文件中需要去定义一个CreateLaptop函数实现服务
func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error) 
	return nil, nil

5.3、完善服务端代码

5.3.1、通过req获取Laptop结构体

  1. 由于在proto中定义的Request消息携带Laptop结构体,所以可以在函数中调用GetLaptop函数从请求中获取笔记本电脑对象
func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error) 
	laptop := req.GetLaptop()
	log.Printf("receive a create-laptop request with id: %s", laptop.Id)
	return nil, nil

5.3.2、如果客户端已经生成了笔记本电脑ID,检测UUID是否有效

  1. 使用Google UUID包,命令:go get github.com/google/uuid
  2. 使用uuid.Parse()函数来解析笔记本电脑的 ID。
if len(laptop.Id) > 0 
		_, err := uuid.Parse(laptop.Id)
		if err != nil 
			return nil, status.Errorf(codes.InvalidArgument, "laptop ID is not a valid UUID: %v", err)
		
	

5.3.3、如果客户端没有发送笔记本电脑 ID,我们将在服务器上使用uuid.NewRandom()命令生成它。

else 
		id, err := uuid.NewRandom()
		if err != nil 
			return nil, status.Errorf(codes.Internal, "cannot generate a new laptop ID: %v", err)
		
		laptop.Id = id.String()
	

5.3.4、将获取到的Laptop进行存储

  1. 创建新文件service/laptop_store.go
  2. 定义一个LaptopStore接口,此接口需要实现一个save方法,这样后面可以定义存储在文件中的结构体与存储在数据库中的结构体
type LaptopStore interface 
	save(laptop *pb.Laptop) error

  1. 实现接口,定义为存储在磁盘上的结构体
    • 需要一个读写互斥体来处理多个并发请求以保存笔记本电脑
type InMemoryLaptopStore struct 
	mutex sync.RWMutex
	data  map[string]*pb.Laptop

  1. 声明一个函数NewInMemoryLaptopStore来返回一个新的 InMemoryLaptopStore,并初始化其中的数据映射
func NewInMemoryLaptopStore() *InMemoryLaptopStore 
	return &InMemoryLaptopStore
		data: make(map[string]*pb.Laptop),
	

  1. 实现InMemoryLaptopStore结构体的save功能
    • 需要在添加新对象之前获取写锁
    • 我们检查笔记本电脑 ID 是否已经存在于地图中
    • 如果笔记本电脑不存在,先深度拷贝这个Laptop结构体后将此结构体进行存储
func (store *InMemoryLaptopStore) Save(laptop *pb.Laptop) error 
	store.mutex.Lock()
	defer store.mutex.Unlock()

	if store.data[laptop.Id] != nil 
		return errors.New("record already exists")
	

	other,_ := deepCopy(laptop)

	store.data[other.Id] = other

	return nil

  1. 实现deepCopy函数
    • 使用包,命令:go get github.com/jinzhu/copier
func deepCopy(laptop *pb.Laptop) (*pb.Laptop, error) 
    other := &pb.Laptop

    err := copier.Copy(other, laptop)
    if err != nil 
        return nil, fmt.Errorf("cannot copy laptop data: %w", err)
    

    return other, nil

5.3.5、在server端使用存储结构体进行存储

  1. 在laptop_server.go文件中的LaptopServer结构体添加新字段LaptopServer
type LaptopServer struct 
	pb.UnimplementedLaptopServiceServer
	laptopStore LaptopStore

  1. CreateLaptop()函数中,我们可以调用server.laptopStore.Save()将输入的笔记本电脑保存到商店
    • 使用codes.Internal获取保存错误
    • 调用errors.Is()函数检查是否存在
err := server.laptopStore.save(laptop)
	if err != nil 
		code := codes.Internal
		if errors.Is(err, errors.New("record already exists")) 
			code = codes.AlreadyExists
		
		return nil, status.Errorf(code, "cannot save laptop to the store: %v", err)
	
  1. 最后,如果没有发生错误,我们可以使用笔记本电脑 ID 创建一个新的响应对象并将其返回给调用者
log.Printf("saved laptop with id: %s", laptop.Id)

	res := &pb.CreateLaptopResponse
		Id: laptop.Id,
	

5.3.6、完整的server端代码

func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error) 
	laptop := req.GetLaptop()
	log.Printf("receive a create-laptop request with id: %s", laptop.Id)
	if len(laptop.Id) > 0 
		_, err := uuid.Parse(laptop.Id)
		if err != nil 
			return nil, status.Errorf(codes.InvalidArgument, "laptop ID is not a valid UUID: %v", err)
		
	 else 
		id, err := uuid.NewRandom()
		if err != nil 
			return nil, status.Errorf(codes.Internal, "cannot generate a new laptop ID: %v", err)
		
		laptop.Id = id.String()
	

	err := server.laptopStore.save(laptop)
	if err != nil 
		code := codes.Internal
		if errors.Is(err, errors.New("record already exists")) 
			code = codes.AlreadyExists
		
		return nil, status.Errorf(code, "cannot save laptop to the store: %v", err)
	

	log.Printf("saved laptop with id: %s", laptop.Id)

	res := &pb.CreateLaptopResponse
		Id: laptop.Id,
	
	return res, nil

5.4、编写主服务器和客户端

  1. 创建一个新cmd文件夹,创建cmd/server/main.go文件与cmd/client/main.go文件
  2. 编写Makefile文件
server:
	go run cmd/server/main.go

client:
	go run cmd/client/main.go
  1. 编写服务端代码
func main() 
	laptopServer := service.NewLaptopServer(service.NewInMemoryLaptopStore())
	grpcServer := grpc.NewServer()
	pb.RegisterLaptopServiceServer(grpcServer, laptopServer)
	listener, _ := net.Listen("tcp", ":8888")
	grpcServer.Serve(listener)

以上是关于第五节——实现一元GRPC的主要内容,如果未能解决你的问题,请参考以下文章

第五节——实现一元GRPC

CISCO网络基础小实验第五节

栈和队列-第五节:JavaC++Python实现栈和队列

实现和测试-第五节:确认测试

第五节:JQuery框架源码简析

嵌入式Linux从入门到精通之第五节:链表