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 互发送聊天消息。

参考

相关推荐
Villiam_AY17 分钟前
使用 chromedp 高效爬取 Bing 搜索结果
后端·爬虫·golang
CryptoPP18 分钟前
跨境金融数据对接实践:印度NSE/BSE股票行情API集成指南
开发语言·后端·金融
程序员爱钓鱼44 分钟前
Go语言实战案例-实现简易定时提醒程序
后端·google·go
堕落年代1 小时前
Spring Boot HTTP状态码详解
spring boot·后端·http
Victor3561 小时前
Redis(49)Redis哨兵如何实现故障检测和转移?
后端
Victor3561 小时前
Redis(48)Redis哨兵的优点和缺点是什么?
后端
IT_陈寒1 小时前
Python异步编程的7个致命误区:90%开发者踩过的坑及高效解决方案
前端·人工智能·后端
绝无仅有2 小时前
三方系统callback回调MySQL 报错排查与解决:mysql context cancel
后端·面试·github
绝无仅有2 小时前
项目三方合同提交失败的MySQL 错误排查与解决:`context deadline exceeded`
后端·面试·github
W-GEO2 小时前
Spring Boot 源码深度解析:揭秘自动化配置的魔法
spring boot·后端·自动化