gRPC流式示例

gRPC快速入门
在上面的示例中,客户端发起了一个RPC请求到服务端,服务端进行业务处理并返回响应给客户端,这是gRPC最基本的一种工作方式(Unary RPC)。除此之外,依托于HTTP2,gRPC还支持流式RPC(Streaming RPC)。

服务端流式RPC

客户端发出一个RPC请求,服务端与客户端之间建立一个单向的流,服务端可以向流中写入多个响应消息,最后主动关闭流;而客户端需要监听这个流,不断获取响应直到流关闭。应用场景举例:客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。

我们在此编写一个使用多种语言打招呼的方法,客户端发来一个用户名,服务端分多次返回打招呼的信息。

  1. 定义服务
// 服务端返回流式数据
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  1. 服务端需要实现 LotsOfReplies 方法。
    修改.proto文件后,需要重新使用 protocol buffers编译器生成客户端和服务端代码。
// LotsOfReplies 返回使用多种语言打招呼
func (s *server) LotsOfReplies(in *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {
	words := []string{
		"你好",
		"hello",
		"こんにちは",
		"안녕하세요",
	}

	for _, word := range words {
		data := &pb.HelloResponse{
			Reply: word + in.GetName(),
		}
		// 使用Send方法返回多个数据
		if err := stream.Send(data); err != nil {
			return err
		}
	}
	return nil
}

  1. 客户端调用LotsOfReplies 并将收到的数据依次打印出来
func runLotsOfReplies(c pb.GreeterClient) {
	// server端流式RPC
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	stream, err := c.LotsOfReplies(ctx, &pb.HelloRequest{Name: *name})
	if err != nil {
		log.Fatalf("c.LotsOfReplies failed, err: %v", err)
	}
	for {
		// 接收服务端返回的流式数据,当收到io.EOF或错误时退出
		res, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("c.LotsOfReplies failed, err: %v", err)
		}
		log.Printf("got reply: %q\n", res.GetReply())
	}
}

客户端流式RPC

客户端传入多个请求对象,服务端返回一个响应结果。典型的应用场景举例:物联网终端向服务器上报数据、大数据流式计算等。
在这个示例中,我们编写一个多次发送人名,服务端统一返回一个打招呼消息的程序。

  1. 定义服务
// 客户端发送流式数据
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);

修改.proto文件后,需要重新使用 protocol buffers编译器生成客户端和服务端代码。

  1. 服务端实现LotsOfGreetings方法。
// LotsOfGreetings 接收流式数据
func (s *server) LotsOfGreetings(stream pb.Greeter_LotsOfGreetingsServer) error {
	reply := "你好:"
	for {
		// 接收客户端发来的流式数据
		res, err := stream.Recv()
		if err == io.EOF {
			// 最终统一回复
			return stream.SendAndClose(&pb.HelloResponse{
				Reply: reply,
			})
		}
		if err != nil {
			return err
		}
		reply += res.GetName()
	}
}  
  1. 客户端调用LotsOfGreetings方法,向服务端发送流式请求数据,接收返回值并打印。
func runLotsOfGreeting(c pb.GreeterClient) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	// 客户端流式RPC
	stream, err := c.LotsOfGreetings(ctx)
	if err != nil {
		log.Fatalf("c.LotsOfGreetings failed, err: %v", err)
	}
	names := []string{"七米", "q1mi", "沙河娜扎"}
	for _, name := range names {
		// 发送流式数据
		err := stream.Send(&pb.HelloRequest{Name: name})
		if err != nil {
			log.Fatalf("c.LotsOfGreetings stream.Send(%v) failed, err: %v", name, err)
		}
	}
	res, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("c.LotsOfGreetings failed: %v", err)
	}
	log.Printf("got reply: %v", res.GetReply())
}

双向流式RPC

双向流式RPC即客户端和服务端均为流式的RPC,能发送多个请求对象也能接收到多个响应对象。典型应用示例:聊天应用等。
我们这里还是编写一个客户端和服务端进行人机对话的双向流式RPC示例。

  1. 定义服务
// 双向流式数据
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
  1. 服务端实现BidiHello方法。
// BidiHello 双向流式打招呼
func (s *server) BidiHello(stream pb.Greeter_BidiHelloServer) error {
	for {
		// 接收流式请求
		in, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}

		reply := magic(in.GetName()) // 对收到的数据做些处理

		// 返回流式响应
		if err := stream.Send(&pb.HelloResponse{Reply: reply}); err != nil {
			return err
		}
	}
}

这里我们还定义了一个处理数据的magic函数,其内容如下。

// magic 一段价值连城的“人工智能”代码
func magic(s string) string {
	s = strings.ReplaceAll(s, "吗", "")
	s = strings.ReplaceAll(s, "吧", "")
	s = strings.ReplaceAll(s, "你", "我")
	s = strings.ReplaceAll(s, "?", "!")
	s = strings.ReplaceAll(s, "?", "!")
	return s
}
  1. 客户端调用BidiHello方法,一边从终端获取输入的请求数据发送至服务端,一边从服务端接收流式响应。
func runBidiHello(c pb.GreeterClient) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	// 双向流模式
	stream, err := c.BidiHello(ctx)
	if err != nil {
		log.Fatalf("c.BidiHello failed, err: %v", err)
	}
	waitc := make(chan struct{})
	go func() {
		for {
			// 接收服务端返回的响应
			in, err := stream.Recv()
			if err == io.EOF {
				// read done.
				close(waitc)
				return
			}
			if err != nil {
				log.Fatalf("c.BidiHello stream.Recv() failed, err: %v", err)
			}
			fmt.Printf("AI:%s\n", in.GetReply())
		}
	}()
	// 从标准输入获取用户输入
	reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
	for {
		cmd, _ := reader.ReadString('\n') // 读到换行
		cmd = strings.TrimSpace(cmd)
		if len(cmd) == 0 {
			continue
		}
		if strings.ToUpper(cmd) == "QUIT" {
			break
		}
		// 将获取到的数据发送至服务端
		if err := stream.Send(&pb.HelloRequest{Name: cmd}); err != nil {
			log.Fatalf("c.BidiHello stream.Send(%v) failed: %v", cmd, err)
		}
	}
	stream.CloseSend()
	<-waitc
}

将服务端和客户端的代码都运行起来,就可以实现简单的对话程序了。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
gRPC 中实现文件传输通常需要使用流式传输,因为文件可能非常大,无法一次性传输完成。下面是一个简单的 Java 示例,演示如何使用 gRPC 进行流式文件传输: 1. 定义 gRPC 服务 首先,需要在 .proto 文件中定义一个服务,用于文件传输。例如: ``` service FileTransferService { rpc uploadFile(stream FileChunk) returns (UploadResponse) {} } message FileChunk { bytes data = 1; } message UploadResponse { bool success = 1; } ``` 这个服务包含一个方法 `uploadFile`,用于传输文件。它接收一个 `FileChunk` ,并返回一个 `UploadResponse` 对象。 2. 实现服务 在 Java 中,需要实现 gRPC 服务。可以使用 gRPC 提供的 `StreamObserver` 类来处理流式传输。例如: ``` public class FileTransferServiceImpl extends FileTransferServiceGrpc.FileTransferServiceImplBase { @Override public StreamObserver<FileChunk> uploadFile(StreamObserver<UploadResponse> responseObserver) { return new StreamObserver<FileChunk>() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @Override public void onNext(FileChunk chunk) { try { outputStream.write(chunk.getData().toByteArray()); } catch (IOException e) { onError(e); } } @Override public void onError(Throwable t) { responseObserver.onError(t); } @Override public void onCompleted() { try { // 将文件保存到本地,或者处理文件数据 byte[] fileData = outputStream.toByteArray(); // ... responseObserver.onNext(UploadResponse.newBuilder().setSuccess(true).build()); } catch (Exception e) { onError(e); } finally { responseObserver.onCompleted(); } } }; } } ``` 在 `uploadFile` 方法中,返回一个 `StreamObserver`,用于处理客户端发送的 `FileChunk` 。在 `onNext` 方法中,将每个 `FileChunk` 对象的 `data` 字段写入 ByteArrayOutputStream,直到接收到的结束信号。在 `onError` 方法中,将错误传递给客户端。在 `onCompleted` 方法中,处理文件数据,然后返回一个 `UploadResponse` 对象,表示文件传输成功。 3. 启动 gRPC 服务 最后,需要在 Java 中启动 gRPC 服务。例如: ``` Server server = ServerBuilder.forPort(50051) .addService(new FileTransferServiceImpl()) .build(); server.start(); server.awaitTermination(); ``` 在这个示例中,创建了一个 gRPC 服务器,监听 50051 端口,并将 `FileTransferServiceImpl` 注册为服务。然后启动服务器,并等待终止。 4. 使用客户端发送文件 在客户端使用 gRPC 上传文件,需要创建一个 `StreamObserver` 对象,将 `FileChunk` 对象写入中。例如: ``` ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext().build(); FileTransferServiceGrpc.FileTransferServiceStub stub = FileTransferServiceGrpc.newStub(channel); StreamObserver<FileChunk> requestObserver = stub.uploadFile(new StreamObserver<UploadResponse>() { @Override public void onNext(UploadResponse response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 处理完成信号 } }); // 读取文件数据并写入 byte[] fileData = // 从文件中读取数据 int chunkSize = // 每个 Chunk 的大小 int offset = 0; while (offset < fileData.length) { int length = Math.min(chunkSize, fileData.length - offset); byte[] chunkData = Arrays.copyOfRange(fileData, offset, offset + length); requestObserver.onNext(FileChunk.newBuilder().setData(ByteString.copyFrom(chunkData)).build()); offset += length; } // 发送完成信号 requestObserver.onCompleted(); ``` 在这个示例中,创建了一个 gRPC 客户端,并使用 `FileTransferServiceStub` 创建了一个 `StreamObserver` 对象,用于向服务器发送文件。然后读取文件数据,并将数据分成块写入中。最后发送完成信号。 这是一个简单的 gRPC Java 文件传输示例,用于演示如何使用流式传输实现文件传输。实际上,还可以使用 gRPC 提供的其他功能,如控制、错误处理和安全性,以更好地处理文件传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值