gRPC入门 | 青训营笔记

135 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记

3 gRPC基础

每次需要获取的依赖:

go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf

3.1 gRPC入门

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
//永久性获取

首先在项目文件夹下编写hello.proto

syntax = "proto3";

package helloService;

//在此文件中创建文件夹proto并将生成的文件保存到其中
option go_package = "./proto";

//传输的信息结构
message String {
  string value = 1;
}

//服务
service HelloService {
  rpc Hello (String) returns (String);
}

生成gRPC相关的代码(一定要cd到根目录):

protoc --go_out=. ./源文件名.proto
protoc --go-grpc_out=. ./源文件名.proto

会生成两个文件,重点关注*_grpc.pb.go的文件,我们调用此文件中的方法。

编写服务端的代码如下:

package main

import (
	"RPC_Protobuf/together_helloServe/proto"
	"context"
	"google.golang.org/grpc"
	"log"
	"net"
)

//封装对象实现包外调用--满足.proto文件中***Service的接口
type Service struct {}

//实现helloService服务(根据之前的proto文件和提示写)
func (s *Service)Hello(ctx context.Context, reply *proto.String)(*proto.String,error) {
    //实现业务逻辑
	return &proto.String{
		Value: "Hello "+ reply.Value,
	},nil
}

func main() {
	listener, err := net.Listen("tcp","localhost:1234")
	if err!=nil{
		log.Fatal(err)
	}

	grpcServer := grpc.NewServer()
    
    //注册服务
	proto.RegisterHelloServiceServer(grpcServer,new(Service))

	err = grpcServer.Serve(listener)
	if err!=nil{
		log.Fatal(err)
	}
}

再编写客户端的代码:

package main

import (
	"RPC_Protobuf/together_helloServe/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
)

func main() {
    
    //链接
    //grpc.WithTransportCredentials(insecure.NewCredentials())跳过证书认证
	conn, err := grpc.Dial("localhost:1234",grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err!= nil{
		log.Fatal(err)
	}
	defer conn.Close()

    //创建客户端
	client := proto.NewHelloServiceClient(conn)
    
    //调用服务
	reply, err := client.Hello(context.Background(),&proto.String{Value: "TR"})
	if err != nil {
		log.Fatal(err)
	}
    
    //输出结果
	fmt.Println(reply.GetValue())
}

其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。

3.2 gRPC流

RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,gRPC框架针对服务器端和客户端分别提供了流特性。

只需要再.proto文件中添加channel用于传输即可:

service HelloService {
    rpc Hello (String) returns (String);

    rpc Channel (stream String) returns (stream String);
    //关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流。
}

在生成的文件中可以发现服务端和客户端的流辅助接口均定义了Send和Recv方法用于流数据的双向通信

package main

import (
	"RPC_Protobuf/together_stream/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
	"net"
)

type Service struct {
	proto.UnimplementedHelloServiceServer
}

func (p *Service) Hello (stx context.Context, reply *proto.String) (*proto.String, error) {

	fmt.Println("receive link form client: " + reply.GetValue())

	return &proto.String{Value: reply.GetValue() + " received"},nil
}

func (p *Service) Channel(stream proto.HelloService_ChannelServer) error {
	for {
		args, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				fmt.Println("传输完成")
				return nil
			}
			return err
		}
		fmt.Println("Received from client: " + args.GetValue())

		err = stream.Send(&proto.String{Value: args.GetValue() + " Received;" })
		if err != nil {
			return err
		}
	}
}

func main() {
	lis, err := net.Listen("tcp","localhost:1234")
	if err!=nil{
		log.Fatal(err)
	}

	service := grpc.NewServer()

	proto.RegisterHelloServiceServer(service,new(Service))

	err = service.Serve(lis)
	if err != nil {
		log.Fatalf("grpcServer.Serve err: %v", err)
	}
}

如果在.proto的文件中出现了mustEmbedUnimplementedHelloServiceServer() ,可以在我们自己的接口中嵌套UnimplementedHelloServiceServer的接口对象,即可跳过此函数的实现。

package main

import (
	"RPC_Protobuf/together_stream/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	"sync"
	"time"
)

func main() {
	conn, err := grpc.Dial("localhost:1234",grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}

	client := proto.NewHelloServiceClient(conn)

	stream, err := client.Channel(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	var wg sync.WaitGroup
	wg.Add(2)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		for{
			err := stream.Send(&proto.String{Value: "请求链接"})
			if err !=nil{
				log.Fatal(err)
			}
			time.Sleep(time.Second*2)
		}
	}(&wg)

	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		for{
			res, err := stream.Recv()
			if err !=nil{
				log.Fatal(err)
			}
			fmt.Println(res)
		}
	}(&wg)

	wg.Wait()
}