Go-gRPC示例

前言

我们使用gRPC,一般是把它作为微服务。因为,它与语言无关,可以适配多种语言。它的底层实现,使用的是HTTP/2。

在使用时,我们需要通过"protoc"命令,为我们生成protocol-buffers的相关代码,它还会为我们生成gRPC相关代码。然后分别在客户端、服务端分别使用相应的代码即可。

1.安装"protocol-buffers"代码生成工具、"gRPC"代码生成工具。

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

2.创建一个新的项目

项目名称任意就好,在该项目的根路径下创建一个"proto"的文件夹,用来存放我们的".proto"文件。

这个".proto"文件,其实就是protocol-buffers的文件,它可以为我们不同语言,生成符合protocol-buffers规范的代码。

这种代码一般以"***.pb.go"结尾。protocol-buffers存储结构利于传输,比json效率要更加好。因此,它得到了广泛的使用。

3.创建"product.proto"文件

并把下面的内容复制到该文件中

# proto 语法,有2和3之分,这里我们使用proto3的语法
syntax = "proto3";


# path;name path,是指proto生成代码的保存路径,name是指生成代码的文件名称
option go_package = "../service";

# 指定被生成代码的包名
package service;

# 定义对象、结构体,与我们的JavaBean很相似,这是一个请求对象
message ProductRequest{
  int32 productId = 1;
}

# 定义对象、结构体,与我们的JavaBean很相似,这是一个响应对象
message ProductResponse{
  int32 productStock = 1;
}


# 定义我们的服务,类似于Java的controller
service ProductService{
  // 普通 rpc
  rpc GetProductStock(ProductRequest) returns(ProductResponse);
  // 客户端流式 rpc
  rpc QueryProductStockClientStream(stream ProductRequest) returns(ProductResponse);
  // 服务端流式 rpc
  rpc QueryProductStockServerStream(ProductRequest) returns(stream ProductResponse);
  // 双向流式 rpc
  rpc QueryProductStockStream(stream ProductRequest) returns(stream ProductResponse);
}

4.使用"protoc"生成代码

在执行下面的命令生成之前,请在根目录下,先创建一个"service"的目录,用来保存我们服务的代码。

使用命令行工具,进入到我们项目的根路径下,并执行如下命令:

# protoc protocol buffers命令
# go_out 参数指定pb文件生成目录
# go-grpc_out参数指定服务代码生成目录
# proto/product.proto 指定.proto文件
$ protoc --go_out=./service/ --go-grpc_out=./service/ proto/product.proto 

效果图: 

5.项目下载"gRPC"相关资源到本地

使用命令行工具,进入到更目录下,执行如下命令:

$ go mod tidy

执行后,它就会自动下载相应资源

6.分别在项目根路径创建client、server目录,并在两个目录中都新建"main.go"文件

用来模拟客户端、服务端访问。

客户端代码:

package main

import (
	"context"
	"io"
	"log"
	"math/rand"
	"sync"
	"time"


	"04-stream/service"
	"04-stream/util"
	
	
	
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

var wg = &sync.WaitGroup{}

func getProductStock(client service.ProductServiceClient) {
	defer wg.Done()

	ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second)
	defer cancel()

	response, err := client.GetProductStock(ctx, &service.ProductRequest{ProductId: 100})
	if err != nil {
		log.Fatalln(err)
		return
	}

	log.Println(response.ProductStock)
}

//func queryProductStockStream(client service.ProductServiceClient) {
//	defer wg.Done()
//
//	ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second)
//	defer cancel()
//
//	streamClient, err := client.QueryProductStockClientStream(ctx)
//	if err != nil {
//		log.Fatalln(err)
//		return
//	}
//
//	err = streamClient.Send(&service.ProductRequest{ProductId: 222})
//	if err != nil {
//		log.Fatalln(err)
//		return
//	}
//
//	response, err := streamClient.CloseAndRecv()
//	if err != nil {
//		log.Fatalln(err)
//		return
//	}
//
//	log.Println(response.ProductStock)
//}

func queryProductStockClientStream(client service.ProductServiceClient) {
	defer wg.Done()

	count := 0

	ctx := context.TODO()

	stream, err := client.QueryProductStockClientStream(ctx)
	if err != nil {
		log.Fatalln(err)
		return
	}

	for true {
		count++

		request := &service.ProductRequest{ProductId: rand.Int31()}
		log.Println("send ProductId:", request.ProductId, ", count:", count)

		err = stream.Send(request)
		if err != nil {
			log.Fatalln(err)
			return
		}

		time.Sleep(time.Second)
		if count >= 10 {
			break
		}
	}

	response, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalln(err)
		return
	}
	log.Println("收到服务器的数据:", response.ProductStock)
}

func queryProductStockServerStream(client service.ProductServiceClient) {
	defer wg.Done()

	count := 0

	// 客户端只发送一次
	serverStream, err := client.QueryProductStockServerStream(context.TODO(), &service.ProductRequest{ProductId: rand.Int31()})
	if err != nil {
		log.Fatalln(err)
		return
	}

	// 接收多次
	for {
		count++

		response, err := serverStream.Recv()
		if err == io.EOF {
			log.Println("客户端接收完毕.")
			return
		}
		if err != nil {
			log.Fatalln(err)
			return
		}

		log.Println("客户端收到:", response.ProductStock, count)
	}

}

func queryProductStockStream(client service.ProductServiceClient) {
	defer wg.Done()

	stream, err := client.QueryProductStockStream(context.TODO())
	if err != nil {
		log.Fatalln(err)
		return
	}

	for {
		err := stream.Send(&service.ProductRequest{ProductId: rand.Int31()})
		if err != nil {
			log.Fatalln(err)
			return
		}
		time.Sleep(time.Second)

		response, err := stream.Recv()
		if err != nil {
			log.Fatalln(err)
			return
		}
		log.Println("收到服务端数据:", response.ProductStock)
	}

}

type queryFn func(client service.ProductServiceClient)

func main() {
	qs := []queryFn{
		//getProductStock,
		//queryProductStockClientStream,
		//queryProductStockServerStream,
		queryProductStockStream,
	}

	conn, err := grpc.Dial(util.Address, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalln(err)
		return
	}

	client := service.NewProductServiceClient(conn)

	for _, fn := range qs {
		wg.Add(1)
		go fn(client)
	}

	wg.Wait()
}

服务端代码:

package main

import (
	"04-stream/service"
	"04-stream/util"
	"google.golang.org/grpc"
	"log"
	"net"
)

func main() {
	server := grpc.NewServer()

	service.RegisterProductServiceServer(server, service.ProductService)

	listen, err := net.Listen("tcp", util.Address)
	if err != nil {
		log.Fatalln(err)
		return
	}

	if err = server.Serve(listen); err != nil {
		return
	}

}

service代码:

package service

import (
	"context"
	"io"
	"log"
	"math/rand"
	"time"
)

type productService struct {
}

func (ps *productService) mustEmbedUnimplementedProductServiceServer() {}

// GetProductStock 获取库存
func (ps *productService) GetProductStock(ctx context.Context, request *ProductRequest) (*ProductResponse, error) {
	log.Println("id:", request.ProductId)
	return &ProductResponse{ProductStock: request.ProductId * 123 * rand.Int31()}, nil
}

// QueryProductStockClientStream
// 客户端请求多次,服务器只写一次
func (ps *productService) QueryProductStockClientStream(stream ProductService_QueryProductStockClientStreamServer) error {
	//request, err := stream.Recv()
	//if err != nil {
	//	log.Fatalln(err)
	//	return err
	//}
	//log.Println("stream request, id:", request.ProductId)
	//return stream.SendAndClose(&ProductResponse{ProductStock: request.ProductId * 1234 * rand.Int31()})

	count := 0
	for {
		count++

		request, err := stream.Recv() // 接收多次请求
		if err == io.EOF {
			log.Println("收到EOF,结束读取")

			err = stream.SendAndClose(&ProductResponse{ProductStock: rand.Int31()})
			if err != nil {
				log.Fatalln(err)
				return err
			}

			log.Println("发送完成,结束调用")
			return nil
		}
		if err != nil {
			log.Fatalln(err)
			return err
		}
		log.Println("stream id:", request.ProductId, ", count :", count)
	}
}

// QueryProductStockServerStream
//  客户端只请求一次,服务器写多次
func (ps *productService) QueryProductStockServerStream(request *ProductRequest, stream ProductService_QueryProductStockServerStreamServer) error {
	count := 0

	for {
		count++

		pr := &ProductResponse{ProductStock: request.ProductId * rand.Int31()}
		log.Println("发送给客户端:", pr.ProductStock, count)

		err := stream.Send(pr)
		if err != nil {
			log.Fatalln(err)
			return err
		}

		if count >= 10 {
			// 发送结束
			log.Println("发送结束.")
			return nil
		}

		time.Sleep(1 * time.Second)
	}
}

// QueryProductStockStream 双向流式 rpc
func (ps *productService) QueryProductStockStream(stream ProductService_QueryProductStockStreamServer) error {
	count := 0
	for {
		count++

		request, err := stream.Recv()
		if err != nil {
			log.Fatalln(err)
			return err
		}
		log.Println("收到客户端请求:", request.ProductId, "count:", count)

		time.Sleep(time.Second)
		err = stream.Send(&ProductResponse{ProductStock: request.ProductId})
		if err != nil {
			log.Fatalln(err)
			return err
		}
	}
}

var ProductService = &productService{}

util代码:

package util

const Address = ":8803"

7.最后,先启动服务端,在启动客户端。

客户端可以根据需要,来选择执行需要的方法,在main函数中的qs数组中设定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值