gRPC的stream使用

gRPC的stream介绍

  • gRPC的stream介绍
    • gRPC为什么提供 steam功能?
    • gRPC的stream的分类
      • 一元RPC
      • 客户端流RPC
      • 服务端流RPC
      • 双向流RPC

gRPC的stream介绍

gRPC为什么提供 steam功能?

  • 在以下场景使用unary rpc 可能有如下问题

    • 数据表过大可能造成熟瞬时的压力
    • 服务需要全部数据接收完毕,才能正确回调响应,进行业务处理,不能客户端边发送,服务端边接受。
  • stream rpc 适用于,大规模数据传递,和实时场景。

gRPC的stream的分类

  • stream 分类
    gRPC的stream使用_第1张图片

一元RPC

  • 定义形式
rpc SayHello(HelloRequest) returns (HelloResponse);
  • 描述
    1、客户端发送一个请求给服务端,
    2、得到服务端的一个响应,
    3、就像正常的函数调用
    gRPC的stream使用_第2张图片
  • client
package main

import (
	"context"
	pb "d-grpc/lib/proto/hellowrold"
	"google.golang.org/grpc"
	"log"
)

const (
	address = "localhost:50051"
)

func main() {
	// 链接服务
	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatalf("dit not connetc:%v", err)
		return
	}
	defer conn.Close()
	// 创建一个客户端
	c := pb.NewGreeterClient(conn)
	r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: "world"})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}

	// 打印响应信息
	log.Printf("Greeting: %s", r.GetMessage())
}
  • server
package main

import (
	"context"
	pb "d-grpc/lib/proto/hellowrold"
	"google.golang.org/grpc"
	"log"
	"net"
)

const (
	port = ":50051"
)

//
func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve:%v", err)
	}
}

type server struct {
	// 这个结构体实现了GreeterServer接口,避免service未实现
	pb.UnimplementedGreeterServer
}

func (*server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

客户端流RPC

  • 定义形式
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • 描述
    1、客户端会以流式的方式将数据写到到服务端,(客户端的写和服务端的读都是同时进行的)
    2、客户端完成写的消息后,关闭发送,并等待接收消息
    3、服务端循环读取消息,直到遇到一个io.EOF,并返回响应消息
    4、始终使用一个rpc请求来操作
    gRPC的stream使用_第3张图片
  • client
package main

import (
	"context"
	pb "d-grpc/lib/proto/echo"
	"fmt"
	"google.golang.org/grpc"
	"log"
)

/*
1、客户端会写一个有效的数据到服务端,
2、客户端完成写的消息后,关闭发送,并等待接收消息
3、服务端读取消息,并返回响应消息
4、始终使用一个rpc请求来操作
*/

// 客户端流RPC
func main() {
	clientCon, err := grpc.Dial("127.0.0.1:5001", grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatal(err)
	}
	clientStreamingEcho(pb.NewEchoClient(clientCon))
}

func clientStreamingEcho(client pb.EchoClient) {
	stream, err := client.ClientStreamingEcho(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	for i := 1; i < 10; i++ {
		req := &pb.EchoRequest{
			Message: fmt.Sprintf("%d", i),
		}
		// 会写成 有序的消息发送给给客户端
		if err := stream.Send(req); err != nil {
			log.Fatal(err)
		}
	}
	// 关闭发送 并 接收数据, 让rpc知道我们客户端已经完成写的操作
	resp, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.GetMessage())
}

  • server
package main

import (
	pb "d-grpc/lib/proto/echo"
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
	"net"
	"strings"
)

func main() {
	lis, err := net.Listen("tcp", "127.0.0.1:5001")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterEchoServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatal(err)
	}
}

type server struct {
	pb.UnimplementedEchoServer
}

// https://grpc.io/docs/languages/go/basics/
func (server) ClientStreamingEcho(stream pb.Echo_ClientStreamingEchoServer) error {
	// 循环读取客户端链接信息
	var strList []string
	for {
		// 不断取得客户端请求数据
		req, err := stream.Recv()
		strList = append(strList, req.GetMessage())
		// 代表客户端已经结束发送数据了
		if err == io.EOF {
			fmt.Println(strings.Join(strList, ","))
			return stream.SendAndClose(&pb.EchoResponse{
				Message: "已经全部接受完毕",
			})
		}
		if err != nil {
			return err
		}
	}
}

服务端流RPC

  • 定义形式
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  • 描述
    1、客户端发送一个请求给服务端,得到一个stream
    2、循环从流中读取服务端返回的有序数据
    3、读取消息直到遇到一个io.EOF则表示服务端没后更多的消息
    gRPC的stream使用_第4张图片

  • client

package main

import (
	"context"
	pb "d-grpc/lib/proto/echo"
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
	"strings"
)

/*

 */
func main() {
	clientCon, err := grpc.Dial("127.0.0.1:5001", grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatal(err)
	}

	echoClient := pb.NewEchoClient(clientCon)
	req := &pb.EchoRequest{}
	stream, err := echoClient.ServerStreamingEcho(context.Background(), req)
	if err != nil {
		log.Fatal(err)
	}
	var strList []string
	for {
		resp, err := stream.Recv()
		if err == nil {
			strList = append(strList, resp.GetMessage())
		}
		if err == io.EOF {
			fmt.Println(err)
			break
		}
		if err != nil {
			log.Println(err)
		}
	}
	fmt.Println(strings.Join(strList, ","))

}

  • server
package main

import (
	pb "d-grpc/lib/proto/echo"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
)

func main() {
	lis, err := net.Listen("tcp", "127.0.0.1:5001")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterEchoServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatal(err)
	}
}

type server struct {
	pb.UnimplementedEchoServer
}

func (server) ServerStreamingEcho(req *pb.EchoRequest, stream pb.Echo_ServerStreamingEchoServer) error {
	// 向客户端发送数据
	for i := 1; i < 10; i++ {
		err := stream.Send(&pb.EchoResponse{
			Message: fmt.Sprintf("%d", i),
		})
		if err != nil {
			return err
		}
	}
	// 返回 nil 或者 err就代表服务端响应数据完毕
	return nil
}

双向流RPC

  • 定义形式
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
  • 描述
    双向流RPC,客户端和服务端异步的读取和接收数据
    gRPC的stream使用_第5张图片
  • client
package main

import (
	"context"
	pb "d-grpc/lib/proto/echo"
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
)

// 客户端流RPC
func main() {
	clientCon, err := grpc.Dial("127.0.0.1:5001", grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatal(err)
	}

	bidirectionalStreamingEcho(pb.NewEchoClient(clientCon))
}

func bidirectionalStreamingEcho(client pb.EchoClient) {
	stream, err := client.BidirectionalStreamingEcho(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	// 异步读取服务端消息
	go func() {
		for {
			resp, err := stream.Recv()
			if err == io.EOF {
				close(done)
				return
			}
			if err != nil {
				log.Fatal(err)
			}
			log.Printf("recv-server-%s", resp.GetMessage())
		}
	}()

	// 想服务端发送消息
	for i := 1; i < 10; i++ {
		err := stream.Send(&pb.EchoRequest{
			Message: fmt.Sprintf("%d", i),
		})
		if err != nil {
			log.Fatal(err)
		}
	}
	// 部分发送了-代表客户端的数据已经放完毕了
	_ = stream.CloseSend()

	// 服务端消息接受完毕,退出程序
	<-done
}

  • server
package main

import (
	pb "d-grpc/lib/proto/echo"
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
	"net"
)

func main() {
	lis, err := net.Listen("tcp", "127.0.0.1:5001")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterEchoServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatal(err)
	}
}

type server struct {
	pb.UnimplementedEchoServer
}

func (server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
	for {
		req, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}

		log.Printf("recv-client-%s\n", req.GetMessage())
		// 发送响应的数据
		err = stream.Send(&pb.EchoResponse{
			Message: fmt.Sprintf("server-%s", req.GetMessage()),
		})
		if err != nil {
			return err
		}
	}
}

你可能感兴趣的:(grpc,golang,golang,grpc,stream)