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