Go语言使用GRPC由浅入深

42 篇文章 1 订阅
15 篇文章 3 订阅

Golang中grpc入门

注意:阅读开始前,请确保有grpc相关基础,新手向,使用各种实战方式来学习grpc

一、grpc的四种调用模式

1.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

1.2、单向调用

首先我们初始化一个client对象,以及请求体对象

//初始化一个client对象,conn是用grpc.Dial创建的
	client := streamprotodemo.NewGreeterClient(conn)
	StreamReqData := streamprotodemo.StreamReqData{
		Data: "chenteng",
	}
服务端实现单向调用

func (s * server)Single(ctx context.Context,req *streamprotodemo.StreamReqData) (*streamprotodemo.StreamResData,error){
	fmt.Println("server Single 被调用")
	return &streamprotodemo.StreamResData{
		Data: req.Data +"from server",
	},nil
}
客户端实现单向调用
func Single(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.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)
}

1.3、客户端流模式

服务端实现单向客户端流模式调用
func (s *server) PutStream(clientdata streamprotodemo.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 PutStream(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.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)
		}
	}
}

1.4、服务端流模式

服务端实现单向服务端流模式调用

通过for循环向客户端发送时间戳消息,模拟服务端流

func (s *server) GetStream(req *streamprotodemo.StreamReqData, res streamprotodemo.Greeter_GetStreamServer) error {
	fmt.Println("server GetStream 被调用")
	for i:=0;i<10;i++{
		err := res.Send(&streamprotodemo.StreamResData{
			Data: fmt.Sprintf("%v", time.Now().Unix()),
		})
		if err != nil {
			log.Fatalln("send err", err)
		}
		time.Sleep(time.Second)
	}
	return nil
}
客户端实现单向服务端流模式调用
func GetStream(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.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)
	}
}

1.5、双向流模式

客户端、服务端同时发送消息,所以要使用两个gorouting并发去执行发收功能,这里使用sync下面的Waitgroup来等待gorouting执行完毕

服务端实现双向流模式调用

func (s *server) AllStream(alldata streamprotodemo.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(&streamprotodemo.StreamResData{
				Data: "我是服务器",
			})
			time.Sleep(time.Second)
		}
	}()
	return nil
}

客户端实现双向流模式调用
func AllStream(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.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)
		}
	}()
}

二、grpc中的metadata

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

客户端

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)
}

服务端

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
}

三、grpc中使用拦截器传递metadata

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

客户端

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)
}


服务端

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)
}

四、GRPC返回报错状态码与内容

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

客户端

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)
}

服务端

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)
}

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈小c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值