在 gRPC 中,gRPC ClientStream 允许客户端通过流式传输数据到服务器。此外,gRPC ClientStream 还支持双向流式传输,即客户端和服务器可以同时通过 ClientStream 传输数据。下面是一个简单的示例:
首先,定义一个 gRPC 服务,包含一个客户端流式方法:
service MyService {
rpc MyClientStreamMethod(stream MyRequest) returns (MyResponse) {}
}
在客户端代码中,需要使用 Stub 来调用 MyClientStreamMethod 方法。在客户端调用时,需要使用一个客户端流对象来传递流式数据:
import (
"context"
"google.golang.org/grpc"
)
//创建连接
conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed to dial: %v", err)
}
defer conn.Close()
// 创建客户端 Stub 对象
client := pb.NewMyServiceClient(conn)
// 初始化客户端流对象
stream, err := client.MyClientStreamMethod(context.Background())
if err != nil {
log.Fatalf("%v.MyClientStreamMethod(_) = _, %v", client, err)
}
// 将多个请求数据发送到服务端
for i := 0; i < 10; i++ {
req := &pb.MyRequest{Msg: fmt.Sprintf("Client Request %d", i)}
if err := stream.Send(req); err != nil {
log.Fatalf("%v.Send(%v) = %v", stream, req, err)
}
}
// 结束客户端流,等待服务端响应
resp, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
fmt.Printf("Server Response: %s\n", resp.Msg)
服务端接收客户端流的方式非常类似,只需要在服务端实现MyClientStreamMethod 方法:
import "google.golang.org/grpc"
// 创建 gRPC Server
s := grpc.NewServer()
// 注册 MyService 服务
pb.RegisterMyServiceServer(s, &server{})
// 启动 gRPC Server
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
type server struct{}
func (s *server) MyClientStreamMethod(stream pb.MyService_MyClientStreamMethodServer) error {
// 从客户端流中接收数据
var requests []*pb.MyRequest
for {
req, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
requests = append(requests, req)
}
// 处理接收到的请求数据
var msg string
for _, req := range requests {
msg = fmt.Sprintf("%s %s", msg, req.Msg)
}
// 发送响应
return stream.SendAndClose(&pb.MyResponse{Msg: msg})
}
上面代码中,函数的第一个参数是一个用于接收客户端请求的流对象。我们可以使用 stream.Recv() 函数从客户端流中接收请求数据。当客户端关闭连接时,stream.Recv() 函数会返回一个 io.EOF 错误,此时处理流式请求的过程就结束了。