grpc:流式 RPC

流式Rpc

流式 RPC 允许客户端和服务端之间建立一个持续的数据通道, 可以双向传输数据流,常用于处理大量数据和实现双向通讯的场。本文将简介流式Rpc的三种类型即: 客户端流式、服务端流式、双向流式。

接着我们分别通过一些例子(1、大文件上传 2、广播消息、3、多人聊天)的例子来实践。

定义 proto

首先,创建一个greeter.proto

proto 复制代码
service Greeter {
  // hello
  rpc SayHello (HelloRequest) returns (HelloReply) {
  }
  // 上传文件
  rpc UploadFile(stream FileChunk) returns (UploadReply);
  // 广播
  rpc Broadcast(BroadcastRequest) returns (stream Notice);
  // 聊天
  rpc ChatStream(stream ChatRequest) returns (stream ChatReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

message FileChunk {
  bytes data = 1;
}

message UploadReply {
  string message = 1;
}

message BroadcastRequest {
}

message Notice {
  string content = 1;
}

message ChatRequest {
  string message = 1;
}

message ChatReply {
  string message = 1;
} 

生成对应的go文件,命令参考

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative greeter.prot o

客户端流式 RPC

客户端使用提供的流编写一系列消息并将其发送到服务器。客户端完成编写消息后,它会等待服务器读取所有消息并返回其响应。您可以通过将关键字放在请求 stream类型之前来指定客户端流式方法。 客户端代码如下:

go 复制代码
func UploadFile(client pb.GreeterClient, filePath string) error {
    stream, err := client.UploadFile(context.Background())
    if err != nil {
       return err
    }

    file, err := os.Open(filePath)
    if err != nil {
       return err
    }
    defer file.Close()

    buf := make([]byte, 1024)
    for {
       n, err := file.Read(buf)
       if err == io.EOF {
          break
       }
       if err != nil {
          return err
       }
       if err := stream.Send(&pb.FileChunk{Data: buf[:n]}); err != nil {
          return err
       }
    }

    resp, err := stream.CloseAndRecv()
    if err != nil {
       return err
    }

    fmt.Println(resp.Message)
    return nil
}

greeter.proto的UploadFile所示,rpc的请求为'stream FileChunk',例中模拟将文件分块(文中为1k) 通过 stream传输。

服务端流式 RPC

服务器端流式 RPC ,客户端向服务器发送请求并获取流以读取一系列消息。客户端从返回的流中读取,直到没有更多消息。如您在示例中所见,您可以通过将关键字放在响应 stream类型 之前来指定服务器端流式方法。 服务端代码如下

go 复制代码
func (s *server) Broadcast(in *pb.BroadcastRequest, stream pb.Greeter_BroadcastServer) error {
    // 模拟广播消息
    notices := []string{"Notice 1", "Notice 2", "Notice 3"}
    for _, notice := range notices {
       log.Printf("notice: %v", notice)
       if err := stream.Send(&pb.Notice{Content: notice}); err != nil {
          return status.Errorf(codes.Internal, "Failed to send notice: %v", err)
       }
    }
    return nil
}

如greeter.proto的Broadcast 所示,rpc的返回为'stream Notice'示例中,例中模拟了一个广播消息的场景,将消息列表中的的消息依次 stream.Send 方法将每个消息发送给客户端。

双向流式 RPC

双向流式 RPC ,双方使用读写流发送一系列消息。两个流独立运行,因此客户端和服务器可以按任意顺序进行读写:例如,服务器可以等待接收所有客户端消息后再写入响应,也可以交替读取消息然后写入消息,或者进行其他读写组合。每个流中的消息顺序都会保留。您可以通过将关键字放在请求stream 和响应之前来指定此类方法。

客户端代码如下:

go 复制代码
// 调用 ChatStream
stream, err := c.ChatStream(context.Background())
if err != nil {
    log.Fatalf("could not start chat stream: %v", err)
}

// 发送消息并等待响应
messages := []string{"Hello", "How are you?", "This is fun!"}
for _, msg := range messages {
    if err := stream.Send(&pb.ChatRequest{Message: msg}); err != nil {
       log.Fatalf("could not send message: %v", err)
    }

    resp, err := stream.Recv()
    if err == io.EOF {
       break
    }
    if err != nil {
       log.Fatalf("could not receive message: %v", err)
    }
    fmt.Println("Received:", resp.GetMessage())
}

if err := stream.CloseSend(); err != nil {
    log.Fatalf("could not close send: %v", err)
}

服务端代码如下:

go 复制代码
func (s *server) ChatStream(stream pb.Greeter_ChatStreamServer) error {
    for {
       req, err := stream.Recv()
       if err == io.EOF {
          return nil
       }
       if err != nil {
          return err
       }

       // 处理收到的消息
       log.Printf("Received message: %s", req.GetMessage())

       // 发送响应消息
       if err := stream.Send(&pb.ChatReply{Message: "Server: " + req.GetMessage()}); err != nil {
          return err
       }
    }
}

如greeter.proto的 ChatStream 所示,例中模拟了一个实时聊天的场景 ,客户端和服务端通过stream 互发送聊天消息。

参考

相关推荐
良许Linux2 分钟前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥14 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
颜淡慕潇2 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-9 小时前
验证码机制
前端·后端
超爱吃士力架10 小时前
邀请逻辑
java·linux·后端
AskHarries12 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion13 小时前
Springboot的创建方式
java·spring boot·后端