grpc

268 阅读15分钟

本文以参与[新人创作礼]活动,一起开启掘金创作之路

基础使用

一、grpc-go 安装

go get 命令安装

在网络环境通畅的情况下,可以使用 go get 命令安装 grpc-go 库:

go get -u google.golang.org/grpc

需要注意,很多开发者在使用上述的命令进行安装 grpc-go 库时,往往会遇到网络环境的问题导致下载失败,链接超时报错。经常遇到的错误是:

package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)

如果遇到类似上文中的 timeout 的链接服务器超时的错误,说明是网络环境问题,此时可以通过第二种方案进行安装。

下载方式安装

可以使用 git 工具中的 git clone 命令,将代码从 github 上下载到本地。clone 命令:

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc

上述命令后半部分$GOPATH/src/google.golang.org/grpc 是指定将 grpc-go 代码库下载到特定目录。

依赖配置

如果采用 2.2.2 中的 clone 方式下载安装,因为 grpc-go 库中调用了其他外部库内容,因此,需要额外准备相关的代码库环境。需要准备的库主要是 golang.org 包下的一些库。golang.org 包下的库也可以从 github 上下载,然后放到对应的 golang.org 目录下。

grpc-go 案例

刚刚搭建完 grpc-go 库环境后,还暂时不会写 grpc 的程序。可以通过官方提供的案例进行 grpc 的学习。在下载的 grpc-go 库的根目录中,存在有 examples 目录,存放了官方提供的演示案例。开发者可以通过开发工具进行学习和运行调试。

二、gRPC 框架使用

上节课已经学习了 gRPC 基本知识,对 gRPC 有了初步的认识。本节课通过编程实现 gRPC 编程。

定义服务

我们想要实现的是通过 gRPC 框架进行远程服务调用,首先第一步应该是要有服务。利用之前所掌握的内容,gRPC 框架支持对服务的定义和生成。 gRPC 框架默认使用 protocol buffers 作为接口定义语言,用于描述网络传输消息结构。除此之外,还可以使用 protobuf 定义服务接口。

syntax = "proto3";
package message;

//订单请求参数
message OrderRequest {
    string orderId = 1;
    int64 timeStamp = 2;
}

//订单信息
message OrderInfo {
    string OrderId = 1;
    string OrderName = 2;
    string OrderStatus = 3;
}

//订单服务service定义
service OrderService{
    rpc GetOrderInfo(OrderRequest) returns (OrderInfo);
}

我们通过 proto 文件定义了数据结构的同时,还定义了要实现的服务接口,GetOrderInfo 即是具体服务接口的定义,在 GetOrderInfo 接口定义中,OrderRequest 表示是请求传递的参数,OrderInfo 表示处理结果返回数据参数。

编译.proto 文件

环境准备

定义的 proto 文件需要通过编译,生成 go 语言代码文件,供客户端程序和服务端程序使用。可以安装 go 语言环境中的关于 proto 的插件。

go get -a github.com/golang/protobuf/protoc-gen-go

-a 参数标示下载好后直接做 go install

编译.proto 文件

基本用法

可以通过基本编译命令完成对.proto 文件的编译.基础编译命令如下:

protoc --go_out=. *.proto
gRPC 编译支持

如果定义的.proto 文件,如本案例中所示,定义中包含了服务接口的定义,而我们想要使用 gRPC 框架实现 RPC 调用。开发者可以采用 protocol-gen-go 库提供的插件编译功能,生成兼容 gRPC 框架的 golang 语言代码。只需要在基本编译命令的基础上,指定插件的参数,告知 protoc 编译器即可。具体的编译生成兼容 gRPC 框架的服务代码的命令如下:

protoc --go_out=plugins=grpc:. *.proto

gRPC 实现 RPC 编程

服务接口实现

在.proto 定义好服务接口并生成对应的 go 语言文件后,需要对服务接口做具体的实现。定义服务接口具体由 OrderServiceImpl 进行实现,并实现 GetOrderInfo 详细内容,服务实现逻辑与前文所述内容相同。不同点是服务接口参数的变化。详细代码实现如下:

type OrderServiceImpl struct {
}

//具体的方法实现
func (os *OrderServiceImpl) GetOrderInfo(ctx context.Context, request *message.OrderRequest) (*message.OrderInfo, error) {
	orderMap := map[string]message.OrderInfo{
		"201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
		"201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
		"201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
	}

	var response *message.OrderInfo
	current := time.Now().Unix()
	if (request.TimeStamp > current) {
		*response = message.OrderInfo{OrderId: "0", OrderName: "", OrderStatus: "订单信息异常"}
	} else {
		result := orderMap[request.OrderId]
		if result.OrderId != "" {
			fmt.Println(result)
			return &result, nil
		} else {
			return nil, errors.New("server error")
		}
	}
	return response, nil
}

gRPC 实现服务端

使用 gRPC 框架,首先实现服务端的程序。既然使用 gRPC 框架来实现,就需要调用 gRPC 进行服务方法的注册以及监听的处理。服务注册和监听处理实现如下:

func main() {

	server := grpc.NewServer()

	message.RegisterOrderServiceServer(server, new(OrderServiceImpl))

	lis, err := net.Listen("tcp", ":8090")
	if err != nil {
		panic(err.Error())
	}
	server.Serve(lis)
}

gRPC 实现客户端

实现完服务端以后,实现客户端程序。和服务端程序关系对应,调用 gRPC 框架的方法获取相应的客户端程序,并实现服务的调用,具体编程实现如下:

func main() {

	//1、Dail连接
	conn, err := grpc.Dial("localhost:8090", grpc.WithInsecure())
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	orderServiceClient := message.NewOrderServiceClient(conn)

	orderRequest := &message.OrderRequest{OrderId: "201907300001", TimeStamp: time.Now().Unix()}
	orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest)
	if orderInfo != nil {
		fmt.Println(orderInfo.GetOrderId())
		fmt.Println(orderInfo.GetOrderName())
		fmt.Println(orderInfo.GetOrderStatus())
	}
}

运行程序

经过上述步骤后,程序及逻辑全部开发完成。程序运行,打印如下结果:

201907300001
衣服
已付款

三、第一个程序

protobuf

syntax = "proto3";

option go_package=".;myauth";

message AccountRequest{
    string username = 1;
    string password =2;
}


message MemberInfo{
    string info =1;
}

service GetMember {
    rpc GetMemberInfo(AccountRequest)returns(MemberInfo);
}

server

package main

import (
	"context"
	"fmt"
	myauth "mystudy/proto"
	"net"

	"google.golang.org/grpc"
)

type AuthServer struct{}

func (a *AuthServer) GetMemberInfo(ctx context.Context, req *myauth.AccountRequest) (mya *myauth.MemberInfo, err error) {
	return &myauth.MemberInfo{
		Info: "my name is " + req.Username + "my password is " + req.Password,
	}, nil
}

func main() {
	s := grpc.NewServer()
	myauth.RegisterGetMemberServer(s, new(AuthServer))
	lis, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		fmt.Println(err.Error())
	}
	s.Serve(lis)
}

client

package main

import (
	"context"
	"fmt"

	"google.golang.org/grpc"

	myauth "mystudy/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err.Error())
	}
	defer conn.Close()

	authClient := myauth.NewGetMemberClient(conn)
	authRequest := &myauth.AccountRequest{Username: "wg", Password: "666"}
	info, _ := authClient.GetMemberInfo(context.Background(), authRequest)
	if info != nil {
		fmt.Println(info.Info)
	}
}

四、grpc流

在上节课内容中,我们学习了使用 gRPC 框架实现服务的调用编程。在 gRPC 框架中,诸如上节课我们学习的在客户端与服务端之间通过消息结构体定义的方式来传递数据,我们称之为“单项 RPC”,也称之为简单模式。除此之外,gRPC 中还有数据流模式的 RPC 调用实现,这正是我们本节课要学习的内容。

1.1、服务端流 RPC

在服务端流模式的 RPC 实现中,服务端得到客户端请求后,处理结束返回一个数据应答流。在发送完所有的客户端请求的应答数据后,服务端的状态详情和可选的跟踪元数据发送给客户端。服务端流 RPC 实现案例如下:

1.1.1、服务接口定义

在.proto 文件中定义服务接口,使用服务端流模式定义服务接口,如下所示:

...
//订单服务service定义
service OrderService {
    rpc GetOrderInfos (OrderRequest) returns (stream OrderInfo) {}; //服务端流模式
}

我们可以看到与之前简单模式下的数据作为服务接口的参数和返回值不同的是,此处服务接口的返回值使用了 stream 进行修饰。通过 stream 修饰的方式表示该接口调用时,服务端会以数据流的形式将数据返回给客户端。

1.1.2 编译.proto 文件,生成 pb.go 文件

使用 gRPC 插件编译命令编译.proto 文件,编译命令如下:

protoc --go_out=plugins=grpc:. message.proto

1.1.3 自动生成文件的变化

与数据结构体发送携带数据实现不同的时,流模式下的数据发送和接收使用新的功能方法完成。在自动生成的 go 代码程序当中,每一个流模式对应的服务接口,都会自动生成对应的单独的 client 和 server 程序,以及对应的结构体实现。具体编程如下图所示:

1.1.3.1 服务端自动生成
type OrderService_GetOrderInfosServer interface {
	Send(*OrderInfo) error
	grpc.ServerStream
}

type orderServiceGetOrderInfosServer struct {
	grpc.ServerStream
}

func (x *orderServiceGetOrderInfosServer) Send(m *OrderInfo) error {
	return x.ServerStream.SendMsg(m)
}

流模式下,服务接口的服务端提供 Send 方法,将数据以流的形式进行发送

1.1.3.2 客户端自动生成
type OrderService_GetOrderInfosClient interface {
	Recv() (*OrderInfo, error)
	grpc.ClientStream
}

type orderServiceGetOrderInfosClient struct {
	grpc.ClientStream
}

func (x *orderServiceGetOrderInfosClient) Recv() (*OrderInfo, error) {
	m := new(OrderInfo)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

流模式下,服务接口的客户端提供 Recv()方法接收服务端发送的流数据。

1.1.4 服务编码实现

定义好服务接口并编译生成代码文件后,即可根据规则对定义的服务进行编码实现。具体的服务编码实现如下所示:

//订单服务实现
type OrderServiceImpl struct {
}

//获取订单信息s
func (os *OrderServiceImpl) GetOrderInfos(request *message.OrderRequest, stream message.OrderService_GetOrderInfosServer) error {
	fmt.Println(" 服务端流 RPC 模式")

	orderMap := map[string]message.OrderInfo{
		"201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
		"201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
		"201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
	}
	for id, info := range orderMap {
		if (time.Now().Unix() >= request.TimeStamp) {
			fmt.Println("订单序列号ID:", id)
			fmt.Println("订单详情:", info)
			//通过流模式发送给客户端
			stream.Send(&info)
		}
	}
	return nil
}

GetOrderInfos 方法就是服务接口的具体实现,因为是流模式开发,服务端将数据以流的形式进行发送,因此,该方法的第二个参数类型为 OrderService_GetOrderInfosServer,该参数类型是一个接口,其中包含 Send 方法,允许发送流数据。Send 方法的具体实现在编译好的 pb.go 文件中,进一步调用 grpc.SeverStream.SendMsg 方法。

1.1.5 服务的注册和监听的处理

服务的监听与处理与前文所学内容没有区别,依然是相同的步骤:

func main() {
	server := grpc.NewServer()
	//注册
	message.RegisterOrderServiceServer(server, new(OrderServiceImpl))
	lis, err := net.Listen("tcp", ":8090")
	if err != nil {
		panic(err.Error())
	}
	server.Serve(lis)
}

1.1.6 客户端数据接收

服务端使用 Send 方法将数据以流的形式进行发送,客户端可以使用 Recv()方法接收流数据,因为数据流失源源不断的,因此使用 for 无限循环实现数据流的读取,当读取到 io.EOF 时,表示流数据结束。客户端数据读取实现如下:

...
for {
		orderInfo, err := orderInfoClient.Recv()
		if err == io.EOF {
			fmt.Println("读取结束")
			return
		}
		if err != nil {
			panic(err.Error())
		}
		fmt.Println("读取到的信息:", orderInfo)
	}
...

1.1.7 运行结果

按照先后顺序,依次运行 server.go 文件和 client.go 文件,可以得到运行结果。

1.1.7.1 服务端运行结果
 服务端流 RPC 模式
订单序列号ID: 201907300001
订单详情: {201907300001 衣服 已付款 {} [] 0}
订单序列号ID: 201907310001
订单详情: {201907310001 零食 已付款 {} [] 0}
订单序列号ID: 201907310002
订单详情: {201907310002 食品 未付款 {} [] 0}
1.1.7.2 客户端运行结果
客户端请求RPC调用:服务端流模式
读取到的信息: OrderId:"201907310001" OrderName:"\351\233\266\351\243\237" OrderStatus:"\345\267\262\344\273\230\346\254\276"
读取到的信息: OrderId:"201907310002" OrderName:"\351\243\237\345\223\201" OrderStatus:"\346\234\252\344\273\230\346\254\276"
读取到的信息: OrderId:"201907300001" OrderName:"\350\241\243\346\234\215" OrderStatus:"\345\267\262\344\273\230\346\254\276"
读取结束

1.2、客户端流模式

上文演示的是服务端以数据流的形式返回数据的形式。对应的,也存在客户端以流的形式发送请求数据的形式。

1.2.1 服务接口的定义

与服务端同理,客户端流模式的 RPC 服务声明格式,就是使用 stream 修饰服务接口的接收参数,具体如下所示:

...
//订单服务service定义
service OrderService {
    rpc AddOrderList (stream OrderRequest) returns (OrderInfo) {}; //客户端流模式
}

1.2.2 编译.proto 文件

使用编译命令编译.protow 文件。客户端流模式中也会自动生成服务接口的接口。

1.2.2.1 自动生成的服务流接口实现
type OrderService_AddOrderListServer interface {
	SendAndClose(*OrderInfo) error
	Recv() (*OrderRequest, error)
	grpc.ServerStream
}

type orderServiceAddOrderListServer struct {
	grpc.ServerStream
}

func (x *orderServiceAddOrderListServer) SendAndClose(m *OrderInfo) error {
	return x.ServerStream.SendMsg(m)
}

func (x *orderServiceAddOrderListServer) Recv() (*OrderRequest, error) {
	m := new(OrderRequest)
	if err := x.ServerStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

SendAndClose 和 Recv 方法是客户端流模式下的服务端对象所拥有的方法。

1.2.2.2 自动生成的客户端流接口实现
type OrderService_AddOrderListClient interface {
	Send(*OrderRequest) error
	CloseAndRecv() (*OrderInfo, error)
	grpc.ClientStream
}

type orderServiceAddOrderListClient struct {
	grpc.ClientStream
}

func (x *orderServiceAddOrderListClient) Send(m *OrderRequest) error {
	return x.ClientStream.SendMsg(m)
}

func (x *orderServiceAddOrderListClient) CloseAndRecv() (*OrderInfo, error) {
	if err := x.ClientStream.CloseSend(); err != nil {
		return nil, err
	}
	m := new(OrderInfo)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

Send 和 CloseAndRecv 是客户端流模式下的客户端对象所拥有的方法。

1.2.3 服务的实现

客户端流模式的服务接口具体实现如下:

//订单服务实现
type OrderServiceImpl struct {
}

//添加订单信息服务实现
func (os *OrderServiceImpl) AddOrderList(stream message.OrderService_AddOrderListServer) error {
	fmt.Println(" 客户端流 RPC 模式")

	for {
		//从流中读取数据信息
		orderRequest, err := stream.Recv()
		if err == io.EOF {
			fmt.Println(" 读取数据结束 ")
			result := message.OrderInfo{OrderStatus: " 读取数据结束 "}
			return stream.SendAndClose(&result)
		}
		if err != nil {
			fmt.Println(err.Error())
			return err
		}
		//打印接收到的数据
		fmt.Println(orderRequest)
	}
}

1.2.4 服务的注册和监听处理

依然是采用相同的服务注册和监听处理方式对服务进行注册和监听处理。

func main() {

	server := grpc.NewServer()
	//注册
	message.RegisterOrderServiceServer(server, new(OrderServiceImpl))

	lis, err := net.Listen("tcp", ":8090")
	if err != nil {
		panic(err.Error())
	}
	server.Serve(lis)
}

1.2.5 客户端实现

客户端调用 send 方法流数据到服务端,具体实现如下:

...
//调用服务方法
	addOrderListClient, err := orderServiceClient.AddOrderList(context.Background())
	if err != nil {
		panic(err.Error())
	}
	//调用方法发送流数据
	for _, info := range orderMap {
		err = addOrderListClient.Send(&info)
		if err != nil {
			panic(err.Error())
		}
	}

	for {
		orderInfo, err := addOrderListClient.CloseAndRecv()
		if err == io.EOF {
			fmt.Println(" 读取数据结束了 ")
			return
		}
		if err != nil {
			fmt.Println(err.Error())
		}
		fmt.Println(orderInfo.GetOrderStatus())
	}

1.2.6 程序运行

1.2.6.1 服务端

运行案例,程序输出如下:

 客户端流 RPC 模式
201907300001 衣服 已付款
201907310001 零食 已付款
201907310002 食品 未付款
 读取数据结束
 客户端流 RPC 模式
201907300001 衣服 已付款
201907310001 零食 已付款
201907310002 食品 未付款
 读取数据结束
1.2.6.2 客户端

客户端运行程序输出如下:

客户端请求RPC调用:客户端流模式
 读取数据结束
 读取数据结束了

1.3、双向流模式

上文已经讲过了服务端流模式和客户端流模式。如果将客户端和服务端两种流模式结合起来,就是第三种模式,双向流模式。即客户端发送数据的时候以流数据发送,服务端返回数据也以流的形式进行发送,因此称之为双向流模式。

1.3.1 双向流服务的定义

//订单服务service定义
service OrderService {
    rpc GetOrderInfos (stream OrderRequest) returns (stream OrderInfo) {}; //双向流模式
}

1.3.2 编译.proto 文件

1.3.2.1 服务端接口实现
type OrderService_GetOrderInfosServer interface {
	Send(*OrderInfo) error
	Recv() (*OrderRequest, error)
	grpc.ServerStream
}

type orderServiceGetOrderInfosServer struct {
	grpc.ServerStream
}

func (x *orderServiceGetOrderInfosServer) Send(m *OrderInfo) error {
	return x.ServerStream.SendMsg(m)
}

func (x *orderServiceGetOrderInfosServer) Recv() (*OrderRequest, error) {
	m := new(OrderRequest)
	if err := x.ServerStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}
1.3.2.2 客户端接口实现
type OrderService_GetOrderInfosClient interface {
	Send(*OrderRequest) error
	Recv() (*OrderInfo, error)
	grpc.ClientStream
}

type orderServiceGetOrderInfosClient struct {
	grpc.ClientStream
}

func (x *orderServiceGetOrderInfosClient) Send(m *OrderRequest) error {
	return x.ClientStream.SendMsg(m)
}

func (x *orderServiceGetOrderInfosClient) Recv() (*OrderInfo, error) {
	m := new(OrderInfo)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

1.3.3 服务实现

//实现grpc双向流模式
func (os *OrderServiceImpl) GetOrderInfos(stream message.OrderService_GetOrderInfosServer) error {

	for {
		orderRequest, err := stream.Recv()
		if err == io.EOF {
			fmt.Println(" 数据读取结束 ")
			return err
		}
		if err != nil {
			panic(err.Error())
			return err
		}

		fmt.Println(orderRequest.GetOrderId())
		orderMap := map[string]message.OrderInfo{
			"201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
			"201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
			"201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
		}

		result := orderMap[orderRequest.GetOrderId()]
		//发送数据
		err = stream.Send(&result)
		if err == io.EOF {
			fmt.Println(err)
			return err
		}
		if err != nil {
			fmt.Println(err.Error())
			return err
		}
	}
	return nil
}

1.3.4 服务端及客户端的编程实现

1.3.4.1 服务端实现
func main() {
	server := grpc.NewServer()
	//注册
	message.RegisterOrderServiceServer(server, new(OrderServiceImpl))

	lis, err := net.Listen("tcp", ":8092")
	if err != nil {
		panic(err.Error())
	}
	server.Serve(lis)
}
1.3.4.2 客户端实现
func main() {

	//1、Dail连接
	conn, err := grpc.Dial("localhost:8092", grpc.WithInsecure())
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	orderServiceClient := message.NewOrderServiceClient(conn)

	fmt.Println("客户端请求RPC调用:双向流模式")
	orderIDs := []string{"201907300001", "201907310001", "201907310002"}

	orderInfoClient, err := orderServiceClient.GetOrderInfos(context.Background())
	for _, orderID := range orderIDs {
		orderRequest := message.OrderRequest{OrderId: orderID}
		err := orderInfoClient.Send(&orderRequest)
		if err != nil {
			panic(err.Error())
		}
	}

	//关闭
	orderInfoClient.CloseSend()

	for {
		orderInfo, err := orderInfoClient.Recv()
		if err == io.EOF {
			fmt.Println("读取结束")
			return
		}
		if err != nil {
			return
		}
		fmt.Println("读取到的信息:", orderInfo)
	}
}

五、metadata

5.1 新建matedata

//第一种方法
md:=metadata.New(map[string]string{"key1":"value1","key2":"valu e2"})

//第二种方法
md:=metadata.Pairs(
		"key1","value1",
		"key2","value2"
		)

5.2 发送matedata

md:= metadata.Pairs("key","value1")

// 新建一个有matedata的context
ctx := metadata.NewOutgoingContext(context.Background(),md)

// 单向 RPC
response,err := client.SomeRPC(ctx,someRequest)

5.3 接收metadata

func(s *server) SomeRPC(ctx context.Context,in *pb.SomeRequest)(*pb.SomeResponse,error){
		md,ok := metadata.FromIncomingContext(ctx)
		// 下面些业务逻辑
}

六、拦截器

6.1 服务端拦截器

package main

import (
	"context"
	"fmt"
	myauth "mystudy/proto"
	"net"

	"google.golang.org/grpc"
)

type AuthServer struct{}

func (a *AuthServer) GetMemberInfo(ctx context.Context, req *myauth.AccountRequest) (mya *myauth.MemberInfo, err error) {
	return &myauth.MemberInfo{
		Info: "my name is " + req.Username + "my password is " + req.Password,
	}, nil
}

func main() {
	//  =======================================================================================================
	// 拦截器函数
	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		fmt.Println("这个试拦截器执行逻辑")
		return handler(ctx, req)
	}

	//创建拦截器
	opt := grpc.UnaryInterceptor(interceptor)
	s := grpc.NewServer(opt)

	//可以编写多个拦截器,s := grpc.NewServer(opt,opt1)
	//  =======================================================================================================
	myauth.RegisterGetMemberServer(s, new(AuthServer))
	lis, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		fmt.Println(err.Error())
	}
	s.Serve(lis)
}

6.2 客户端拦截器

package main

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/grpc"

	myauth "mystudy/proto"
)

func main() {
	//  =======================================================================================================
	// 客户端拦截器
	interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		start := time.Now()
		err := invoker(ctx, method, req, reply, cc, opts...)
		fmt.Printf("耗时:%s\n", time.Since(start))
		return err
	}
	opt := grpc.WithUnaryInterceptor(interceptor)

	// grpc.WithInsecure() 已被废止
	// conn, err := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials()), opt)
	conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure(), opt)

	// =======================================================================================================
	if err != nil {
		fmt.Println(err.Error())
	}
	defer conn.Close()

	authClient := myauth.NewGetMemberClient(conn)
	authRequest := &myauth.AccountRequest{Username: "wg111", Password: "666"}
	info, _ := authClient.GetMemberInfo(context.Background(), authRequest)
	if info != nil {
		fmt.Println(info.Info)
	}
}