gRPC 四种流模式概述

gRPC 提供了四种主要的流模式,分别是简单 RPC(Unary RPC)、服务器流式 RPC(Server Streaming RPC)、客户端流式 RPC(Client Streaming RPC)和双向流式 RPC(Bidirectional Streaming RPC)。这些流模式允许客户端和服务器之间以不同的方式进行数据交换。以下是对这四种流模式的介绍:

介绍

1. 简单 RPC (Unary RPC)

简单 RPC 是最基本的 gRPC 模式。客户端发送一个单一请求,服务器返回一个单一响应。这种模式类似于常规的函数调用。

特点:

  • 单次请求和单次响应。
  • 客户端发送请求并等待服务器返回响应。

使用场景:

  • 适用于大多数常规的远程调用操作,例如获取单个用户信息或提交单个订单。
2. 服务器流式 RPC (Server Streaming RPC)

在服务器流式 RPC 中,客户端发送一个请求,服务器返回一个流式响应。服务器可以向客户端发送多个消息,客户端会按顺序接收这些消息。

特点:

  • 客户端发送单次请求。
  • 服务器返回多次响应(流式)。
  • 服务器可以在单次请求中发送多个消息,客户端按顺序接收。

使用场景:

  • 适用于服务器需要发送大量数据或连续数据给客户端的场景,例如实时数据更新、日志流等。
3. 客户端流式 RPC (Client Streaming RPC)

在客户端流式 RPC 中,客户端可以向服务器发送一个流式请求,服务器接收所有请求后,返回一个单一响应。

特点:

  • 客户端发送多次请求(流式)。
  • 服务器返回单次响应。
  • 服务器在接收到所有客户端请求后返回响应。

使用场景:

  • 适用于客户端需要连续发送数据到服务器的场景,例如上传文件、连续传输传感器数据等。
4. 双向流式 RPC (Bidirectional Streaming RPC)

双向流式 RPC 是最复杂的流模式,允许客户端和服务器同时进行流式读写。客户端和服务器都可以随时发送和接收消息。消息的顺序由通信双方决定,具有最大的灵活性。

特点:

  • 客户端和服务器都可以发送和接收多个消息。
  • 双向流式通信,通信双方可以独立地发送和接收数据。
  • 消息传输顺序由客户端和服务器控制。

使用场景:

  • 适用于需要双向实时通信的场景,例如聊天应用、视频会议、实时游戏等。

代码实现

目录树如下:

.
├── go.mod
├── go.sum
└── stream_grpc
    ├── client
    │   └── client.go
    ├── proto
    │   ├── stream_grpc.pb.go
    │   ├── stream.pb.go
    │   └── stream.proto
    └── server
        └── server.go

  1. .proto.代码

    syntax = "proto3";
    
    //option go_package = "stream_grpc/proto";
    
    service StreamService {
      // 服务端流模式
      rpc GetStream(StreamRequestData) returns (stream StreamResponseData);
    
      // 客户端流模式
      rpc PostStream(stream StreamRequestData) returns (StreamResponseData);
    
      // 双向流模式
      rpc AllStream(stream StreamRequestData) returns (stream StreamResponseData);
    }
    
    message StreamRequestData {
      string data = 1;
    }
    
    message StreamResponseData {
      string data = 1;
    }
    
  2. client/client.go

    package main
    
    import (
    	"context"
    	"fmt"
    	"go-try/stream_grpc/proto"
    	"google.golang.org/grpc"
    	"sync"
    	"time"
    )
    
    func main() {
    	conn, err := grpc.NewClient("0.0.0.0:8080", grpc.WithInsecure())
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    
    	cli := proto.NewStreamServiceClient(conn)
    
    	// 服务端流
    	res, _ := cli.GetStream(context.Background(), &proto.StreamRequestData{Data: "hello"})
    	for {
    		recv, err := res.Recv()
    		if err != nil {
    			fmt.Println(err)
    			break
    		}
    		fmt.Println(recv.Data)
    	}
    
    	// 客户端流
    	postStream, _ := cli.PostStream(context.Background())
    	for i := 0; i < 10; i++ {
    		err := postStream.Send(&proto.StreamRequestData{Data: "hello " + fmt.Sprint(i)})
    		if err != nil {
    			fmt.Println("--", err)
    			break
    		}
    		time.Sleep(time.Second)
    	}
    
    	// 双向流
    	allStream, _ := cli.AllStream(context.Background())
    	var wg sync.WaitGroup
    	wg.Add(2)
    
    	go func() {
    		defer wg.Done()
    		for {
    			_ = allStream.Send(&proto.StreamRequestData{Data: "你好,我是客户端!"})
    			time.Sleep(time.Second)
    		}
    	}()
    
    	go func() {
    		defer wg.Done()
    		for {
    			recv, _ := allStream.Recv()
    			fmt.Println("收到服务端消息: ", recv.Data)
    		}
    	}()
    
    	wg.Wait()
    }
    
    
  3. server/server.go

    package main
    
    import (
    	"fmt"
    	"go-try/stream_grpc/proto"
    	"google.golang.org/grpc"
    	"net"
    	"sync"
    	"time"
    )
    
    type Server struct {
    	proto.UnimplementedStreamServiceServer
    }
    
    // 服务端流
    func (s *Server) GetStream(req *proto.StreamRequestData,
    	stream proto.StreamService_GetStreamServer) error {
    	// 模拟返回stream
    	for i := 0; i < 10; i++ {
    		err := stream.Send(&proto.StreamResponseData{Data: req.Data + " " + fmt.Sprint(i)})
    		if err != nil {
    			return err
    		}
    
    		time.Sleep(time.Second)
    	}
    	return nil
    }
    
    // 客户端流
    func (s *Server) PostStream(cliStream proto.StreamService_PostStreamServer) error {
    	for {
    		recv, err := cliStream.Recv()
    		if err != nil {
    			fmt.Println(err)
    			break
    		}
    		fmt.Println(recv.Data)
    	}
    	return nil
    }
    
    // 双向流
    func (s *Server) AllStream(allStream proto.StreamService_AllStreamServer) error {
    	var wg sync.WaitGroup
    	wg.Add(2)
    	// 返回stream
    	go func() {
    		defer wg.Done()
    		for {
    			_ = allStream.Send(&proto.StreamResponseData{Data: "你好!我是服务端!"})
    			time.Sleep(time.Second)
    		}
    	}()
    
    	go func() {
    		defer wg.Done()
    		for {
    			recv, _ := allStream.Recv()
    			fmt.Println("收到客户端消息: ", recv.Data)
    		}
    	}()
    
    	wg.Wait()
    	return nil
    }
    
    func main() {
    	listen, err := net.Listen("tcp", "0.0.0.0:8080")
    	if err != nil {
    		panic("failed to listen" + err.Error())
    	}
    
    	server := grpc.NewServer()
    	proto.RegisterStreamServiceServer(server, &Server{})
    
    	if err = server.Serve(listen); err != nil {
    		panic("failed to start grpc" + err.Error())
    	}
    
    }
    
    

总结

流模式客户端请求次数服务器响应次数描述
简单 RPC11单次请求和单次响应
服务器流式 RPC1多次单次请求和多次响应(流式)
客户端流式 RPC多次1多次请求和单次响应(流式)
双向流式 RPC多次多次多次请求和多次响应(流式)

通过选择合适的流模式,gRPC 可以灵活地满足不同的应用需求,从简单的请求-响应模型到复杂的实时双向通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值