1. Go & grpc的使用

欢迎进入 gRPC 的开发文档,gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。

gRPC 是什么?

在 gRPC 里_客户端_应用可以像调用本地对象一样直接调用另一台不同的机器上_服务端_应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个_服务_,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个_存根_能够像服务端一样的方法。

gRPC 客户端和服务端可以在多种环境中运行和交互 - 从 google 内部的服务器到你自己的笔记本,并且可以用任何 gRPC 支持的语言来编写。所以,你可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。此外,Google 最新 API 将有 gRPC 版本的接口,使你很容易地将 Google 的功能集成到你的应用里。

protocol buffers

gRPC 默认使用 _protocol buffers_,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。正如你将在下方例子里所看到的,你用 proto files 创建 gRPC 服务,用 protocol buffers 消息类型来定义方法参数和返回类型。你可以在 Protocol Buffers 文档找到更多关于 Protocol Buffers 的资料。

Protocol buffers 版本

尽管 protocol buffers 对于开源用户来说已经存在了一段时间,例子内使用的却一种名叫 proto3 的新风格的 protocol buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。当前针对 Java 和 C++ 发布了 beta 版本,针对 JavaNano(即 Android Java)发布 alpha 版本,在protocol buffers Github 源码库里有 Ruby 支持, 在golang/protobuf Github 源码库里还有针对 Go 语言的生成器, 对更多语言的支持正在开发中。 你可以在 proto3 语言指南里找到更多内容, 在与当前默认版本的发布说明比较,看到两者的主要不同点。更多关于 proto3 的文档很快就会出现。虽然你_可以_使用 proto2 (当前默认的 protocol buffers 版本), 我们通常建议你在 gRPC 里使用 proto3,因为这样你可以使用 gRPC 支持全部范围的的语言,并且能避免 proto2 客户端与 proto3 服务端交互时出现的兼容性问题,反之亦然。
Protocol Buffer 官网文档整理

gRPC 使用

环境搭建

1. protoc安装

git下载地址:https://github.com/protocolbuffers/protobuf/releases

安装完成后,使用protoc –version

2.Go插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1

go get google.golang.org/grpc

使用

1. 准备工作

编写proto文件
syntax = "proto3";
// 服务端流模式
option go_package = "./;streamprotodemo";

service Greeter {
  rpc Single(StreamReqData) returns (StreamResData); //单向调用
  rpc GetStream(StreamReqData) returns (stream StreamResData);  // 服务端流模式
  rpc PutStream(stream StreamReqData) returns (StreamResData);  // 客户端流模式
  rpc AllStream(stream StreamReqData) returns (stream StreamResData); // 双向流模式
}

message StreamReqData {
  string  data =1 ;
}

message StreamResData {
  string data =1 ;
}
生成grpc-go文件

protoc -I . –go_out=. stream.proto –go-grpc_out=. stream.proto

2. 单向调用

服务端代码:
package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "grpc-study/demo2/proto"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) Single(ctx context.Context, req *pb.StreamReqData) (*pb.StreamResData, error) {
	fmt.Println("server Single 被调用")
	return &pb.StreamResData{
		Data: req.Data + "from server",
	}, nil
}

func main() {
	listen, err := net.Listen("tcp", ":9090")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// 4、创建监听
	err = s.Serve(listen)
	if err != nil {
		fmt.Println(err.Error())
	}
}
客户端代码
package main

import (
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "grpc-study/demo2/proto"
)

func main() {
	// 1、创建一个链接
	conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println(err.Error())
	}
	defer conn.Close()
	// 2、创建一个客户端
	client := pb.NewGreeterClient(conn)
	StreamReqData := &pb.StreamReqData{}
	StreamReqData.Data = "client single func "
	res, err := client.Single(context.Background(), StreamReqData)
	if err != nil {
		log.Fatalln("single call fail ", err)
	}
	fmt.Println("Single call res :", res)

}

3. 客户端流模式

服务端代码:
package main

import (
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "grpc-study/demo2/proto"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) PutStream(clientdata pb.Greeter_PutStreamServer) error {
	fmt.Println("server PutStream 被调用")
	for {
		a, err := clientdata.Recv()
		if err != nil {
			log.Println(err)
			break
		}
		fmt.Println(a)
	}
	return nil
}

func main() {
	listen, err := net.Listen("tcp", ":9090")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// 4、创建监听
	err = s.Serve(listen)
	if err != nil {
		fmt.Println(err.Error())
	}
}
客户端代码
package main

import (
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "grpc-study/demo2/proto"
)

func main() {
	// 1、创建一个链接
	conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println(err.Error())
	}
	defer conn.Close()
	// 2、创建一个客户端
	client := pb.NewGreeterClient(conn)
	StreamReqData := &pb.StreamReqData{}
	putres, _ := client.PutStream(context.Background())
	for i := 0; i < 10; i++ {
		StreamReqData.Data = fmt.Sprintf("NO:%d, 客户端流模式", i)
		fmt.Println("向服务端发送", StreamReqData.Data)
		err := putres.Send(StreamReqData)
		if err != nil {
			log.Fatalln("向服务端发送数据失败", err)
		}
	}
}

4. 服务端流模式

服务端代码:
package main

import (
	"fmt"
	"log"
	"net"
	"time"

	"google.golang.org/grpc"
	pb "grpc-study/demo2/proto"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) GetStream(req *pb.StreamReqData, res pb.Greeter_GetStreamServer) error {
	fmt.Println("server GetStream 被调用")
	for i := 0; i < 10; i++ {
		err := res.Send(&pb.StreamResData{
			Data: fmt.Sprintf("%v", time.Now().Unix()),
		})
		if err != nil {
			log.Fatalln("send err", err)
		}
		time.Sleep(time.Second)
	}
	return nil
}

func main() {
	listen, err := net.Listen("tcp", ":9090")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// 4、创建监听
	err = s.Serve(listen)
	if err != nil {
		fmt.Println(err.Error())
	}
}
客户端代码
package main

import (
	"context"
	"fmt"
	"io"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "grpc-study/demo2/proto"
)

func main() {
	// 1、创建一个链接
	conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println(err.Error())
	}
	defer conn.Close()
	// 2、创建一个客户端
	client := pb.NewGreeterClient(conn)
	StreamReqData := &pb.StreamReqData{}
	//服务端流模式
	res, _ := client.GetStream(context.TODO(), StreamReqData)
	for {
		data, err := res.Recv()
		if err != nil {
			if err == io.EOF {
				fmt.Println("服务端传输结束")
				break
			}
			log.Fatalln(err)
		}
		fmt.Println(data)
	}
}

4. 双向流模式

服务端代码:
package main

import (
	"fmt"
	"log"
	"net"
	"sync"
	"time"

	"google.golang.org/grpc"
	pb "grpc-study/demo2/proto"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) AllStream(alldata pb.Greeter_AllStreamServer) error {
	fmt.Println("server AllStream 被调用")
	wg := sync.WaitGroup{}
	wg.Add(2)
	defer wg.Wait()
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			data, _ := alldata.Recv()
			fmt.Println("AllStream收到客户端消息", data)
		}
	}()
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			_ = alldata.Send(&pb.StreamResData{
				Data: "我是服务器",
			})
			time.Sleep(time.Second)
		}
	}()
	return nil
}

func main() {
	listen, err := net.Listen("tcp", ":9090")
	if err != nil {
		log.Fatal(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// 4、创建监听
	err = s.Serve(listen)
	if err != nil {
		fmt.Println(err.Error())
	}
}
客户端代码
package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "grpc-study/demo2/proto"
)

func AllStream(client pb.GreeterClient, StreamReqData *pb.StreamReqData) {
	//双向流模式
	allstream, _ := client.AllStream(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(2)
	defer wg.Wait()
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			StreamResData, _ := allstream.Recv()
			fmt.Println("AllStream收到服务端消息", StreamResData.Data)
		}
	}()
	go func() {
		defer wg.Done()
		StreamReqData.Data = "AllStream:我是客户端"
		for i := 0; i < 10; i++ {
			_ = allstream.Send(StreamReqData)
			time.Sleep(time.Second)
		}
	}()
	wg.Wait()
}

func main() {
	// 1、创建一个链接
	conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println(err.Error())
	}
	defer conn.Close()
	// 2、创建一个客户端
	client := pb.NewGreeterClient(conn)
	StreamReqData := &pb.StreamReqData{}
	AllStream(client, StreamReqData)
}

5.grpc中的metadata

gRPC让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中,都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串数组类型。metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用。

服务端
func (h *HelloServer)SayHello(ctx context.Context,req *metadatademo.HelloRequest) (*metadatademo.HelloReply,error){
	md , ok := metadata.FromIncomingContext(ctx)
	if !ok {
		fmt.Println("获取md失败")
	}
	if name,ok := md["name"];ok {
		fmt.Println(name[0])
	}
	//for key,val := range md {
	//	fmt.Println(key,val)
	//}
	return &metadatademo.HelloReply{
		Message:  "Hello " + req.Name,
	},nil
}
客户端
func main(){
    //md := metadata.Pairs("timestamp",time.Now().Format(timeformat))

    lis ,_ := grpc.Dial("127.0.0.1:8081",grpc.WithInsecure())
    client := metadatademo.NewHelloServiceClient(lis)
    req := metadatademo.HelloRequest{Name: "chenteng"}

    md := metadata.New(map[string]string{
        "name":"chenteng",
    })
    ctx := metadata.NewOutgoingContext(context.Background(),md)
    res,_ := client.SayHello(ctx,&req)
    fmt.Println(res.Message)
}

6. grpc中使用拦截器传递metadata

在Grpc中可使用拦截器来进行服务鉴权,在收到请求之前就对metadata中进行校验,斌不会侵入我们的业务代码

服务端
package main

import (
	"context"
	"errors"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"log"
	"mygrpc/protodemo"
	"net"
	"time"
)

type HelloServer struct {
	protodemo.UnimplementedHelloServiceServer
}

func (h *HelloServer) SayHello(ctx context.Context, req *protodemo.HelloRequest) (*protodemo.HelloReply, error) {
	time.Sleep(time.Second)
	return &protodemo.HelloReply{
		Message: "Hello " + req.Name,
	}, nil
}

func main() {
	Interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {

		if md, ok := metadata.FromIncomingContext(ctx); !ok {
			log.Println("获取认证信息失败")
			return nil, errors.New("获取认证信息失败")
		} else {
			if value, ok := md["name"]; !ok {
				log.Println("认证信息中未存放name相关信息")
				return nil, errors.New("认证信息中未存放name相关信息")
			} else {
				log.Println("客户端访问,name = ", value[0])
			}
		}


		starttime := time.Now()
		res, err := handler(ctx, req)
		fmt.Println("请求已完成,耗时:", time.Since(starttime))
		return res, err
	}
	md := grpc.UnaryInterceptor(Interceptor)
	server := grpc.NewServer(md)
	protodemo.RegisterHelloServiceServer(server, &HelloServer{})
	lis, err := net.Listen("tcp", ":7789")
	if err != nil {
		log.Fatal(err)
		return
	}
	_ = server.Serve(lis)
}
客户端
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"mygrpc/protodemo"
)

type customCredential struct {}

func (c *customCredential)GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error){
	return map[string]string{
		"name":"chenteng",
	},nil
}

func (c *customCredential)RequireTransportSecurity() bool{
	//是否启用安全模式
	return false
}


func main() {
	//使用更简单的方式传递md
	op := grpc.WithPerRPCCredentials(&customCredential{})
	conn, _ := grpc.Dial("127.0.0.1:7789", grpc.WithInsecure(), op)
	client := protodemo.NewHelloServiceClient(conn)
	req := protodemo.HelloRequest{Name: "chenteng"}
	res, _ := client.SayHello(context.Background(), &req)
	fmt.Println(res.Message)
}

7.GRPC返回报错状态码与内容

在开发过程中,我们可以使用grpc下面的status包来返回类似于http中的404,500之类的错误状态码,可以方便我们排查问题,光有状态码是不够的,需要携带一下报错信息,下面是client与server的具体实现

服务端
package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"log"
	"mygrpc/protodemo"
	"net"
)

type HelloServer struct {
	protodemo.UnimplementedHelloServiceServer
}

func (h *HelloServer) SayHello(ctx context.Context, req *protodemo.HelloRequest) (*protodemo.HelloReply, error) {
	return nil,status.Error(codes.Internal,"内部错误")
	//return &protodemo.HelloReply{
	//	Message: "Hello " + req.Name,
	//}, nil
}

func main() {
	server := grpc.NewServer()
	protodemo.RegisterHelloServiceServer(server, &HelloServer{})
	lis, err := net.Listen("tcp", ":7790")
	defer lis.Close()
	if err != nil {
		log.Fatal(err)
		return
	}
	_ = server.Serve(lis)
}

客户端
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
	"mygrpc/protodemo"
)

func main() {
	lis, _ := grpc.Dial("127.0.0.1:7789", grpc.WithInsecure())
	client := protodemo.NewHelloServiceClient(lis)
	req := protodemo.HelloRequest{Name: "chenteng"}
	res, err := client.SayHello(context.Background(), &req)
	if err != nil {
		if statusinfo,ok := status.FromError(err);ok {
			fmt.Println(statusinfo.Code())
			fmt.Println(statusinfo.Message())
		}
	}
	fmt.Println(res)
}

参考原文链接[陈小c]:https://blog.csdn.net/weixin_43143310/article/details/125064588


   转载规则


《1. Go & grpc的使用》 by XieJiayi is licensed under a 知识共享署名 4.0 国际许可协议 许可协议。转载请注明来源
  目录