一元 RPC

PPG007 ... 2022-5-6 About 2 min

# 一元 RPC

在 proto 文件编译后生成的代码中可以找到 proto 中 service 关键字定义的接口 interface:

type LaptopServiceServer interface {
	CreateLaptop(context.Context, *CreateLaptopRequest) (*CreateLaptopResponse, error)
	mustEmbedUnimplementedLaptopServiceServer()
}
1
2
3
4

在生成的接口中存在一个对包外不可见的方法,同时这个生成的文件提供了一个结构体 UnimplementedLaptopServiceServer 实现了这个接口的所有方法,如果我们要在其他包中实现这个接口,就必须在包外的结构体中:

type LaptopServer struct {
  // 自定义的接口,用来保存笔记本,这里直接保存在一个 map 中。
	LaptopStore LaptopStore
  // 自定义的接口,用来保存图片,这里保存在系统中指定的路径中。
	ImageStore  ImageStore
  // 匿名嵌套
	pb.UnimplementedLaptopServiceServer
  // 自定义接口,用来保存笔记本的评分,这里直接存在 map 中。
	RatingStore RatingStore
}
1
2
3
4
5
6
7
8
9
10

在上面的结构体上实现这个接口:

func (server *LaptopServer) CreateLaptop(ctx context.Context, req *pb.CreateLaptopRequest) (*pb.CreateLaptopResponse, error) {
  // 获取请求中的 laptop 对象
	laptop := req.GetLaptop()
	log.Printf("receive a create-laptop request with id: %s", laptop.Id)
  // 如果传来的 laptop 的 id 大于 0 就对其进行验证。
	if len(laptop.Id) > 0 {
    // 使用 uuid 解析 id
		_, err := uuid.Parse(laptop.Id)
    // 如果出错就直接返回
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "laptop ID is not a valid UUID: %v", err)
		}
	} else {
    // 如果发来的 laptop 的 id 长度为 0,就生成一个随机 id 赋值给这个 laptop 对象。
		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()
	}
  // 如果客户端终止了(Ctrl + C)就直接返回错误。
	if ctx.Err() == context.Canceled {
		log.Println("request canceled")
		return nil, status.Error(codes.Canceled, "request canceled")
	}
  // 如果客户端设置了超时且已经超时也返回错误。
	if ctx.Err() == context.DeadlineExceeded {
		log.Println("deadline exceeded")
		return nil, status.Error(codes.DeadlineExceeded, "deadling exceeded")
	}
  // 将这个 laptop 保存起来并对错误进行处理。
	err := server.LaptopStore.Save(laptop)
	if err != nil {
		s, ok := status.FromError(err)
		if !ok {
			return nil, status.Errorf(codes.Internal, "cannot save laptop to the store: %v", err)
		}
		return nil, status.Errorf(s.Code(), "cannot save laptop to the store: %v", err)
	}
	return &pb.CreateLaptopResponse{
		Id: laptop.Id,
	}, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

服务端启动函数:

func main() {
	port := flag.Int("port", 8080, "the server port")
	flag.Parse()
	log.Printf("start server on port %d\n", *port)
	laptopServer := service.NewLaptopServer(service.NewInMemoryLaptopStore())
  // 创建一个 grpc 服务器。
	grpcServer := grpc.NewServer()
  // 向这个 grpc 服务器注册一个服务。
	pb.RegisterLaptopServiceServer(grpcServer, laptopServer)
	address := fmt.Sprintf("0.0.0.0:%d", *port)
  // 定义监听。
	listener, err := net.Listen("tcp", address)
	if err != nil {
		log.Fatalf("cannot start server: %v\n", err)
	}
  // 启动 grpc 服务器。
	err = grpcServer.Serve(listener)
	if err != nil {
		log.Fatalf("cannot start server: %v\n", err)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

客户端启动函数:

func main() {
	serverAddress := flag.String("address", "", "the server address")
	flag.Parse()
	log.Printf("dial server %s\n", *serverAddress)
  // 不使用 TLS 进行连接。
	conn, err := grpc.Dial(*serverAddress, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("cannot dial server: %v\n", err)
	}
  // 调用生成的方法生成指定服务的客户端。
	laptopClient := pb.NewLaptopServiceClient(conn)
  // 创建一个 laptop 对象。
	laptop := sample.NewLaptop()
  // 构建一个请求对象。
	req := &pb.CreateLaptopRequest{
		Laptop: laptop,
	}
  // 设置超时时间。
	ctx, cancelFunction := context.WithTimeout(context.Background(), time.Second * 5)
	defer cancelFunction()
  // 通过 client 调用方法
	res, err := laptopClient.CreateLaptop(ctx, req)
	if err !=nil {
		log.Fatalf("call createLaptop error: %v\n", err)
	}
	log.Printf("created laptop with id: %s\n", res.Id)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27