grpc介绍(三)——流传输

定义

使用stream关键字修饰来表示流程传输。

  • 当该关键字修饰参数时,表示为客户端流式的gRPC接口;
// 普通RPC
rpc SimplePing(PingRequest) returns (PingReply);

// 客户端流式RPC
rpc SimplePing(stream PingRequest) returns (PingReply);
  • 当该参数修饰返回值时,表示为服务端流式的gRPC接口;
// 服务端流式RPC
rpc SimplePing(PingRequest) returns (stream PingReply);
  • 当该参数修饰参数和返回值时,表示为双向流式的gRPC接口;
// 双向流式RPC
rpc SimplePing(stream PingRequest) returns (stream PingReply);

客户端流

1)修改.proto文件

// Product.proto

syntax = "proto3";

option go_package="../service";

package service;

message ProductRequest {
	int32 prod_id = 1;
}

message ProductReponse {
	int32 prod_stock = 1;
}

service ProdService {
	rpc GetProductStock(ProductRequest) returns(ProductReponse);
	
	// 增加客户端流定义方法
	rpc UpdateProductStockClientStream(stream ProductRequest) returns(ProductReponse);
}

2)修改接口实现文件

// product.go

package service

import (
	"context"

	"io"

	"fmt"
)

var ProductService = &productService{}

type productService struct {
}

// 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductReponse, error) {
	stock := p.GetStockById(request.ProdId)
	return &ProductReponse{ProdStock: stock}, nil
}

// 新增客户端流实现接口
func (p *productService) UpdateProductStockClientStream(stream ProdService_UpdateProductStockClientStreamServer) error {
	count := 0
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("服务端接收到的流", recv.ProdId)
		count++
		if count > 10 {
			rsq := &ProductReponse{ProdStock: recv.ProdId}
			err := stream.SendAndClose(rsq)
			if err != nil {
				return err
			}

			return nil
		}
	}
}

func (p *productService) GetStockById(id int32) int32 {
	return id
}

3)修改客户端文件

// grpc_client.go

package main

import (
	"google.golang.org/grpc"

	"log"

	"service"

	"context"

	"fmt"

	"google.golang.org/grpc/credentials"

	"crypto/tls"

	"crypto/x509"

	"io/ioutil"

	"auth"

	"time"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.dong.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.dong.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8800", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProdServiceClient(conn)

	// 方法请求
	// resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	// if err != nil {
	// 	log.Fatal("调用gRPC方法失败: ", err)
	// }

	// fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)

	// 获取流
	stream, err := productServiceClient.UpdateProductStockClientStream(context.Background())
	if err != nil {
		log.Fatal("获取流失败", err)
	}

    // 定义切片,设置请求
	rsq := make(chan struct{}, 1)
	go prodRequest(stream, rsq)
    
    // 等待数据接收
	select {
	case <-rsq:
        // 接收数据
		recv, err := stream.CloseAndRecv()
		if err != nil {
			log.Fatal(err)
		}

		stock := recv.ProdStock
		fmt.Println("客户端收到响应: ", stock)
	}
}

// 请求接口
func prodRequest(stream service.ProdService_UpdateProductStockClientStreamClient, rsq chan struct{}) {
	count := 0
	for {
		request := &service.ProductRequest{
			ProdId: 100,
		}

        // 发送数据
		err := stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}

		time.Sleep(time.Second)
		count++
		if count > 10 {
			rsq <- struct{}{}
			break
		}
	}
}

4)测试

先后启动服务端和客户端,打印如下:

服务端:

在这里插入图片描述

客户端:

在这里插入图片描述

服务端流

1)修改.proto文件

//新增服务端流定义方法

syntax = "proto3";

option go_package="../service";

package service;

message ProductRequest {
	int32 prod_id = 1;
}

message ProductReponse {
	int32 prod_stock = 1;
}

service ProdService {
	rpc GetProductStock(ProductRequest) returns(ProductReponse);
	
	// 客户端流定义方法
	rpc UpdateProductStockClientStream(stream ProductRequest) returns(ProductReponse);
	
	// 服务端流定义方法
	rpc GetProductStockServerStream(ProductRequest) returns(stream ProductReponse); 
}

2)修改接口实现文件

// product.go

package service

import (
	"context"

	"io"

	"fmt"

	"time"
)

var ProductService = &productService{}

type productService struct {
}

// 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductReponse, error) {
	stock := p.GetStockById(request.ProdId)
	return &ProductReponse{ProdStock: stock}, nil
}

// 新增客户端接口实现
func (p *productService) UpdateProductStockClientStream(stream ProdService_UpdateProductStockClientStreamServer) error {
	count := 0
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("服务端接收到的流", recv.ProdId)
		count++
		if count > 10 {
			rsq := &ProductReponse{ProdStock: recv.ProdId}
			err := stream.SendAndClose(rsq)
			if err != nil {
				return err
			}

			return nil
		}
	}
}

// 新增服务端接口实现
func (p *productService) GetProductStockServerStream(request *ProductRequest, stream ProdService_GetProductStockServerStreamServer) error {
	count := 0
	for {
		rsp := &ProductReponse{ProdStock: request.ProdId}
		err := stream.Send(rsp)
		if err != nil {
			return err
		}
		time.Sleep(time.Second)
		count++
		if count > 10 {
			return nil
		}
	}
}

func (p *productService) GetStockById(id int32) int32 {
	return id
}

3)修改客户端文件

package main

import (
	"google.golang.org/grpc"

	"log"

	"service"

	"context"

	"fmt"

	"google.golang.org/grpc/credentials"

	"crypto/tls"

	"crypto/x509"

	"io/ioutil"

	"io"

	"auth"

	"time"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.dong.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.dong.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8800", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProdServiceClient(conn)

	// 方法请求
	// resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	// if err != nil {
	// 	log.Fatal("调用gRPC方法失败: ", err)
	// }

	// fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)

	// 获取流
	// stream, err := productServiceClient.UpdateProductStockClientStream(context.Background())
	// if err != nil {
	// 	log.Fatal("获取流失败", err)
	// }

	// rsq := make(chan struct{}, 1)
	// go prodRequest(stream, rsq)
	// select {
	// case <-rsq:
	// 	recv, err := stream.CloseAndRecv()
	// 	if err != nil {
	// 		log.Fatal(err)
	// 	}

	// 	stock := recv.ProdStock
	// 	fmt.Println("客户端收到响应: ", stock)
	// }

	request := &service.ProductRequest{
		ProdId: 100,
	}

    // 调用服务端接口获取流
	stream, err := productServiceClient.GetProductStockServerStream(context.Background(), request)
	if err != nil {
		log.Fatal("获取流失败")
	}

    // 循环获取流
	for {
		recv, err := stream.Recv()
		if err != nil {
            // 流数据接收完成标志
			if err == io.EOF {
				fmt.Println("客户端接收数据完成")
				stream.CloseSend()
				break
			}

			log.Fatal(err)
		}
		fmt.Println("客户端收到的流", recv.ProdStock)
	}
}

func prodRequest(stream service.ProdService_UpdateProductStockClientStreamClient, rsq chan struct{}) {
	count := 0
	for {
		request := &service.ProductRequest{
			ProdId: 100,
		}

		err := stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}

		time.Sleep(time.Second)
		count++
		if count > 10 {
			rsq <- struct{}{}
			break
		}
	}
}

4)测试

先后启动服务端和客户端,打印如下:

服务端:

在这里插入图片描述

客户端

在这里插入图片描述

双向流

1)修改.proto文件

// 说明: 新增双向流定义方法

syntax = "proto3";

option go_package="../service";

package service;

message ProductRequest {
	int32 prod_id = 1;
}

message ProductReponse {
	int32 prod_stock = 1;
}

service ProdService {
	rpc GetProductStock(ProductRequest) returns(ProductReponse);
	
	// 客户端流定义方法
	rpc UpdateProductStockClientStream(stream ProductRequest) returns(ProductReponse);
	
	// 服务端流定义方法
	rpc GetProductStockServerStream(ProductRequest) returns(stream ProductReponse); 
	
	// 双向流定义方法
	rpc SayHelloStream(stream ProductRequest) returns(stream ProductReponse); 
}

2)修改接口实现文件

package service

import (
	"context"

	"io"

	"fmt"

	"time"
)

var ProductService = &productService{}

type productService struct {
}

// 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductReponse, error) {
	stock := p.GetStockById(request.ProdId)
	return &ProductReponse{ProdStock: stock}, nil
}

func (p *productService) UpdateProductStockClientStream(stream ProdService_UpdateProductStockClientStreamServer) error {

	count := 0
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("服务端接收到的流", recv.ProdId)
		count++
		if count > 10 {
			rsq := &ProductReponse{ProdStock: recv.ProdId}
			err := stream.SendAndClose(rsq)
			if err != nil {
				return err
			}

			return nil
		}
	}
}

func (p *productService) GetProductStockServerStream(request *ProductRequest, stream ProdService_GetProductStockServerStreamServer) error {

	count := 0
	for {
		rsp := &ProductReponse{ProdStock: request.ProdId}
		err := stream.Send(rsp)
		if err != nil {
			return err
		}
		time.Sleep(time.Second)
		count++
		if count > 10 {
			return nil
		}
	}
}

func (p *productService) SayHelloStream(stream ProdService_SayHelloStreamServer) error {
	for {
		// 接收消息
		recv, err := stream.Recv()
		if err != nil {
			return nil
		}

		fmt.Println("服务端接收客户端的消息", recv.ProdId)
		time.Sleep(time.Second)

		// 发送消息
		rsp := &ProductReponse{ProdStock: recv.ProdId}
		err = stream.Send(rsp)
		if err != nil {
			return err
		}
	}
}

func (p *productService) GetStockById(id int32) int32 {
	return id
}

3)修改客户端文件

package main

import (
	"google.golang.org/grpc"

	"log"

	"service"

	"context"

	"fmt"

	"google.golang.org/grpc/credentials"

	"crypto/tls"

	"crypto/x509"

	"io/ioutil"

	//	"io"

	"auth"

	"time"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.dong.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.dong.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8800", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProdServiceClient(conn)

	// 方法请求
	// resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	// if err != nil {
	// 	log.Fatal("调用gRPC方法失败: ", err)
	// }

	// fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)

	// 获取流
	// stream, err := productServiceClient.UpdateProductStockClientStream(context.Background())
	// if err != nil {
	// 	log.Fatal("获取流失败", err)
	// }

	// rsq := make(chan struct{}, 1)
	// go prodRequest(stream, rsq)
	// select {
	// case <-rsq:
	// 	recv, err := stream.CloseAndRecv()
	// 	if err != nil {
	// 		log.Fatal(err)
	// 	}

	// 	stock := recv.ProdStock
	// 	fmt.Println("客户端收到响应: ", stock)
	// }

	// request := &service.ProductRequest{
	// 	ProdId: 100,
	// }

	// stream, err := productServiceClient.GetProductStockServerStream(context.Background(), request)
	// if err != nil {
	// 	log.Fatal("获取流失败")
	// }

	// for {
	// 	recv, err := stream.Recv()
	// 	if err != nil {
	// 		if err == io.EOF {
	// 			fmt.Println("客户端接收数据完成")
	// 			stream.CloseSend()
	// 			break
	// 		}

	// 		log.Fatal(err)
	// 	}
	// 	fmt.Println("客户端收到的流", recv.ProdStock)
	// }

    // 获取双向流
	stream, err := productServiceClient.SayHelloStream(context.Background())

	for {
		// 发送消息
		request := &service.ProductRequest{
			ProdId: 100,
		}
		err = stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}
		time.Sleep(time.Second)
		recv, err := stream.Recv()
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("客户端接收服务端的消息", recv.ProdStock)
	}
}

func prodRequest(stream service.ProdService_UpdateProductStockClientStreamClient, rsq chan struct{}) {
	count := 0
	for {
		request := &service.ProductRequest{
			ProdId: 100,
		}

		err := stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}

		time.Sleep(time.Second)
		count++
		if count > 10 {
			rsq <- struct{}{}
			break
		}
	}
}

4)测试

先后启动服务端和客户端,打印如下:

服务端:

在这里插入图片描述

客户端:

在这里插入图片描述

类似于心跳的功能。

部分参考:
HTTP/2介绍:https://blog.youkuaiyun.com/qq_34827674/article/details/115188949

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值