gRPC的简单应用

news2024/9/29 13:21:23

gRPC的简单应用

gRPC是由开发的一个高性能、通用的开源RPC框架,主要面向移动应用开发且基于HTTP/2协议标准而设计,同时支持大多数流行的编程语言。
官网:https://grpc.io/

安装protoc 工具

https://protobuf.dev/

安装Go插件

旧版本直接安装protoc-gen-go即可

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

新版本须同时安装protoc-gen-go,protoc-gen-go-grpc

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

以下的过程使用旧版本演示,新版本在新版Go插件章节演示

grpc sample

1.创建pb/product.proto文件

product.proto

syntax = "proto3";

option go_package="../service";

package service;

message ProductRequest {
  int32 prod_id = 1;
}

message ProductResponse {
  int32 prod_stock = 1;
}

// 定义接口
service ProductService {
  rpc GetProductStock(ProductRequest) returns(ProductResponse);
}

2.cd 到pb目录执行

protoc --go_out=plugins=grpc:./ product.proto

3.生成了 /service/product.pb.go

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.26.0
// 	protoc        v3.21.12
// source: product.proto

package service

import (
	context "context"
	grpc "google.golang.org/grpc"
	codes "google.golang.org/grpc/codes"
	status "google.golang.org/grpc/status"
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type ProductRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	ProdId int32 `protobuf:"varint,1,opt,name=prod_id,json=prodId,proto3" json:"prod_id,omitempty"`
}

func (x *ProductRequest) Reset() {
	*x = ProductRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_product_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *ProductRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*ProductRequest) ProtoMessage() {}

func (x *ProductRequest) ProtoReflect() protoreflect.Message {
	mi := &file_product_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use ProductRequest.ProtoReflect.Descriptor instead.
func (*ProductRequest) Descriptor() ([]byte, []int) {
	return file_product_proto_rawDescGZIP(), []int{0}
}

func (x *ProductRequest) GetProdId() int32 {
	if x != nil {
		return x.ProdId
	}
	return 0
}

type ProductResponse struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	ProdStock int32 `protobuf:"varint,1,opt,name=prod_stock,json=prodStock,proto3" json:"prod_stock,omitempty"`
}

func (x *ProductResponse) Reset() {
	*x = ProductResponse{}
	if protoimpl.UnsafeEnabled {
		mi := &file_product_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *ProductResponse) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*ProductResponse) ProtoMessage() {}

func (x *ProductResponse) ProtoReflect() protoreflect.Message {
	mi := &file_product_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use ProductResponse.ProtoReflect.Descriptor instead.
func (*ProductResponse) Descriptor() ([]byte, []int) {
	return file_product_proto_rawDescGZIP(), []int{1}
}

func (x *ProductResponse) GetProdStock() int32 {
	if x != nil {
		return x.ProdStock
	}
	return 0
}

var File_product_proto protoreflect.FileDescriptor

var file_product_proto_rawDesc = []byte{
	0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
	0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x64,
	0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x72,
	0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x72, 0x6f,
	0x64, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65,
	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x5f, 0x73,
	0x74, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64,
	0x53, 0x74, 0x6f, 0x63, 0x6b, 0x32, 0x56, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,
	0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x72,
	0x6f, 0x64, 0x75, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x12, 0x17, 0x2e, 0x73, 0x65, 0x72,
	0x76, 0x69, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75,
	0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x50, 0x72,
	0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, 0x5a,
	0x0a, 0x2e, 0x2e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f,
	0x74, 0x6f, 0x33,
}

var (
	file_product_proto_rawDescOnce sync.Once
	file_product_proto_rawDescData = file_product_proto_rawDesc
)

func file_product_proto_rawDescGZIP() []byte {
	file_product_proto_rawDescOnce.Do(func() {
		file_product_proto_rawDescData = protoimpl.X.CompressGZIP(file_product_proto_rawDescData)
	})
	return file_product_proto_rawDescData
}

var file_product_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_product_proto_goTypes = []interface{}{
	(*ProductRequest)(nil),  // 0: service.ProductRequest
	(*ProductResponse)(nil), // 1: service.ProductResponse
}
var file_product_proto_depIdxs = []int32{
	0, // 0: service.ProductService.GetProductStock:input_type -> service.ProductRequest
	1, // 1: service.ProductService.GetProductStock:output_type -> service.ProductResponse
	1, // [1:2] is the sub-list for method output_type
	0, // [0:1] is the sub-list for method input_type
	0, // [0:0] is the sub-list for extension type_name
	0, // [0:0] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { file_product_proto_init() }
func file_product_proto_init() {
	if File_product_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_product_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*ProductRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_product_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*ProductResponse); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_product_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   2,
			NumExtensions: 0,
			NumServices:   1,
		},
		GoTypes:           file_product_proto_goTypes,
		DependencyIndexes: file_product_proto_depIdxs,
		MessageInfos:      file_product_proto_msgTypes,
	}.Build()
	File_product_proto = out.File
	file_product_proto_rawDesc = nil
	file_product_proto_goTypes = nil
	file_product_proto_depIdxs = nil
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6

// ProductServiceClient is the client API for ProductService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ProductServiceClient interface {
	GetProductStock(ctx context.Context, in *ProductRequest, opts ...grpc.CallOption) (*ProductResponse, error)
}

type productServiceClient struct {
	cc grpc.ClientConnInterface
}

func NewProductServiceClient(cc grpc.ClientConnInterface) ProductServiceClient {
	return &productServiceClient{cc}
}

func (c *productServiceClient) GetProductStock(ctx context.Context, in *ProductRequest, opts ...grpc.CallOption) (*ProductResponse, error) {
	out := new(ProductResponse)
	err := c.cc.Invoke(ctx, "/service.ProductService/GetProductStock", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// ProductServiceServer is the server API for ProductService service.
type ProductServiceServer interface {
	GetProductStock(context.Context, *ProductRequest) (*ProductResponse, error)
}

// UnimplementedProductServiceServer can be embedded to have forward compatible implementations.
type UnimplementedProductServiceServer struct {
}

func (*UnimplementedProductServiceServer) GetProductStock(context.Context, *ProductRequest) (*ProductResponse, error) {
	return nil, status.Errorf(codes.Unimplemented, "method GetProductStock not implemented")
}

func RegisterProductServiceServer(s *grpc.Server, srv ProductServiceServer) {
	s.RegisterService(&_ProductService_serviceDesc, srv)
}

func _ProductService_GetProductStock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(ProductRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(ProductServiceServer).GetProductStock(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/service.ProductService/GetProductStock",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(ProductServiceServer).GetProductStock(ctx, req.(*ProductRequest))
	}
	return interceptor(ctx, in, info, handler)
}

var _ProductService_serviceDesc = grpc.ServiceDesc{
	ServiceName: "service.ProductService",
	HandlerType: (*ProductServiceServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "GetProductStock",
			Handler:    _ProductService_GetProductStock_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "product.proto",
}

4.创建/service/product.go实现Productserveice

package service

import "context"

var ProductService = &productService{}

type productService struct {
}

// GetProductStock 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
	stock := p.GetStockById(request.ProdId)
	return &ProductResponse{ProdStock: stock}, nil
}

func (p *productService) GetStockById(id int32) int32 {
	return 100
}

5.创建服务端代码

main.go

package main

import (
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
	"test_grpc/service"
)

func main() {
	// 创建rpc实例
	rpcServer := grpc.NewServer()

	// 服务注册
	service.RegisterProductServiceServer(rpcServer, service.ProductService)

	// 启动监听
	listener, err := net.Listen("tcp", ":8000")
	if err != nil {
		log.Fatal("启动监听失败", err)
	}

	// 启动服务
	err = rpcServer.Serve(listener)
	if err != nil {
		log.Fatal("启动服务失败", err)
	}
}

6.将/service目录复制到client目录下

client引入service包中的方法,进行使用

7.创建客户端代码 /client/main.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	"test_grpc/client/service"
)

func main() {
	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	res, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 520})
	if err != nil {
		log.Fatal("调用gRPC方法失败: ", err)
	}

	fmt.Println("调用gRPC方法成功, ProdStock = ", res.ProdStock)
}

8.分别运行服务端与客户端

客户端返回:
调用gRPC方法成功, ProdStock = 100

9.目录结构

在这里插入图片描述

加密通信与认证方式

HTTP是明文传输的,即客户端与服务端之间通信的信息是可见的,这就存在被窃听、冒充或篡改的风险。HTTPS在HTTP和TCP之间加入了TLS协议。

[HTTP]			[HTTP]
				[SSL/TLS]
[TCP]			[TCP]
[IP]			[IP]
[MAC]			[MAC]

TLS协议主要解决了以下三个网络安全问题:
1.信息加密: HTTP 交互信息是被加密的,第三方就无法被窃取;
2.校验机制:校验信息传输过程中是否有被第三方篡改过,如果被篡改过,则会有警告提示;
3.身份证书:双方认证,双方都可以配置证书,防止身份被冒充;
客户端与服务端通过gRPC进行方法调用,也需要加入证书来保证调用的安全。

0.安装openssl工具

安装openssl:http://slproweb.com/products/Win32OpenSSL.html
并将openssl加入环境变量

创建cert目录,以下所有操作均在该目录中进行

1.生成私钥文件

openssl genrsa -des3 -out ca.key 2048

2.创建证书请求

openssl req -new -key ca.key -out ca.csr

3.生成ca.crt

openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

4.修改openssl.cnf

在openssl安装目录中查找openssl.cnf,复制到当前目录下

# 打开copy_extensions
copy_extensions = copy

# 打开req_extensions
req_extensions = v3_req

# 找到[ v3_req ], 添加
subjectAltName = @alt_names

# 添加标签
[ alt_names ]
DNS.1 = *.test.com

5.生成证书私钥

openssl genpkey -algorithm RSA -out server.key

6.通过私钥生成证书请求文件

openssl req -new -nodes -key server.key -out server.csr -days 3650 -config ./openssl.cnf -extensions v3_req

7.生成SAN证书

openssl x509 -req -days 365 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req

SAN(Subject Alternative Name) 是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

说明:
key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密;
csr:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名;
crt:由颁发证书机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息;
pem:是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER;

单向认证

流程:
1.发送客户端SSL版本等信息到服务端
2.服务端给客户端返回SSL版本,随机数等信息,以及服务器公钥
3.客户端校验服务端证书是否合法,合法继续,否则警告
4.客户端发送自己可支持的对称加密方案给服务端,供其选择
5.服务端选择加密程度高的加密方式
6.服务端将选择好的加密方式以明文方式发送给客户端
7.客户端收到加密方式后,产生随机码,作为对称加密密钥,使用服务端公钥进行加密后,发送给服务端
8.服务端使用私钥对加密信息进行解密,获得对称加密的密钥
9.双端通信,对称加密确保了通信安全

1.修改服务端代码

package main

import (
	"test_grpc/service"

	"google.golang.org/grpc"

	"net"

	"log"

	"fmt"

	"google.golang.org/grpc/credentials"
)

func main() {
	fmt.Println("开始启动服务")

	// 添加证书
	creds, err := credentials.NewServerTLSFromFile("cert/server.pem", "cert/server.key")
	if err != nil {
		log.Fatal("证书获取失败", err)
	}
	// 创建rpc实例(添加认证)
	rpcServer := grpc.NewServer(grpc.Creds(creds))
	// 服务注册
	service.RegisterProductServiceServer(rpcServer, service.ProductService)
	// 启动监听
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		log.Fatal("启动监听失败", err)
	}
	// 启动服务
	err = rpcServer.Serve(listener)
	if err != nil {
		log.Fatal("启动服务失败", err)
	}
	fmt.Println("启动服务成功")
}

2.修改客户端代码

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
	"test_grpc/client/service"
)

func main() {
	// 添加公钥
	creds, err := credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")
	if err != nil {
		log.Fatal("证书错误: ", err)
	}

	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	if err != nil {
		log.Fatal("调用gRPC方法失败: ", err)
	}

	fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)
}

3.使用命令行启动服务端与客户端

客户端执行结果为:
证书认证通过
调用gRPC方法成功, ProdStock = 100

注意:credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")*.test.com应与证书中的域名相对应。

4.目录结构

在这里插入图片描述

双向认证

流程:
1.发送客户端SSL版本等信息到服务端
2.服务端给客户端返回SSL版本,随机数等信息,以及服务器公钥
3.客户端校验服务端证书是否合法,合法继续,否则警告
4.客户端校验通过后,将自己的证书及公钥发送至服务端
5.服务端对客户端证书进行校验,校验结束后获得客户端公钥
6.客户端发送自己可支持的对称加密方案给服务端,供其选择
7.服务端选择加密程度高的加密方式
8.服务端将选择好的加密方式使用客户端公钥进行加密后发送给客户端
9.客户端收到加密方式后,产生随机码,作为对称加密密钥,使用服务端公钥进行加密后,发送给服务端
10.服务端使用私钥对加密信息进行解密,获得对称加密的密钥
11.双端通信,对称加密确保了通信安全

1.生成客户端公钥和私钥

1.1生成私钥

openssl genpkey -algorithm RSA -out client.key

1.2生成证书

openssl req -new -nodes -key client.key -out client.csr -days 3650 -config ./openssl.cnf -extensions v3_req

1.3生成SAN证书

openssl x509 -req -days 365 -in client.csr -out client.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req

2.修改服务端代码

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
	"net"
	"test_grpc/service"
)

func main() {
	fmt.Println("开始启动服务")

	// 添加证书
	// creds, err0 := credentials.NewServerTLSFromFile("cert/server.pem", "cert/server.key")
	// if err0 != nil {
	// 	log.Fatal("证书生成失败", err0)
	// }

	// 证书认证-双向认证
	cert, err0 := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		// 要求必须校验客户端的证书
		ClientAuth: tls.RequireAndVerifyClientCert,
		// 设置根证书的集合, 校验方式使用ClientAuth中设定的模式
		ClientCAs: certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	// 创建rpc实例
	rpcServer := grpc.NewServer(grpc.Creds(creds))

	// 服务注册
	service.RegisterProductServiceServer(rpcServer, service.ProductService)

	// 启动监听
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		log.Fatal("启动监听失败", err)
	}

	// 启动服务
	err = rpcServer.Serve(listener)
	if err != nil {
		log.Fatal("启动服务失败", err)
	}
}

3.修改客户端代码

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
	"test_grpc/client/service"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.test.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	if err != nil {
		log.Fatal("调用gRPC方法失败: ", err)
	}

	fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)
}

4.使用命令行启动服务端与客户端

客户端代码运行结果为:
证书读取成功
ca证书读取成功
设置TLS的TransportCredentials选项成功
证书认证通过
调用gRPC方法成功, ProdStock = 100

5.目录结构

在这里插入图片描述

Token认证

服务端添加用户名密码的校验
或使用jwt或oauth2

1.修改服务端代码

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"io/ioutil"
	"log"
	"net"
	"test_grpc/service"
)

func main() {
	fmt.Println("开始启动服务")

	// 添加证书
	// creds, err0 := credentials.NewServerTLSFromFile("cert/server.pem", "cert/server.key")
	// if err0 != nil {
	// 	log.Fatal("证书生成失败", err0)
	// }

	// 证书认证-双向认证
	cert, err0 := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		// 要求必须校验客户端的证书
		ClientAuth: tls.RequireAndVerifyClientCert,
		// 设置根证书的集合, 校验方式使用ClientAuth中设定的模式
		ClientCAs: certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	// token认证 -- 合法的用户名和密码
	var authInterceptor grpc.UnaryServerInterceptor
	authInterceptor = func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (resp interface{}, err error) {
		// 拦截请求, 验证token
		err = Auth(ctx)
		if err != nil {
			return
		}

		// 继续处理请求
		return handler(ctx, req)
	}

	// 创建rpc实例
	rpcServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(authInterceptor))

	// 服务注册
	service.RegisterProductServiceServer(rpcServer, service.ProductService)

	// 启动监听
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		log.Fatal("启动监听失败", err)
	}

	// 启动服务
	err = rpcServer.Serve(listener)
	if err != nil {
		log.Fatal("启动服务失败", err)
	}
}

func Auth(ctx context.Context) error {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return fmt.Errorf("missing credentials")
	}

	var user string
	var passwd string

	if val, ok := md["user"]; ok {
		user = val[0]
	}

	if val, ok := md["passwd"]; ok {
		passwd = val[0]
	}

	if user != "admin" || passwd != "admin@123" {
		return status.Errorf(codes.Unauthenticated, "token认证失败")
	}

	fmt.Println("token认证成功")
	return nil
}

2.在客户端创建/auth目录 auth.go 文件,实现grpc.PerRPCCredentials接口

package auth

import "context"

type Authentication struct {
	User   string
	Passwd string
}

func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
	map[string]string, error,
) {
	return map[string]string{"user": a.User, "passwd": a.Passwd}, nil
}

func (a *Authentication) RequireTransportSecurity() bool {
	return false
}

3.修改客户端代码

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
	"test_grpc/client/auth"
	"test_grpc/client/service"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.test.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	if err != nil {
		log.Fatal("调用gRPC方法失败: ", err)
	}

	fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)
}

4.使用命令行启动服务端与客户端

客户端执行结果:
证书读取成功
ca证书读取成功
设置TLS的TransportCredentials选项成功
证书认证通过
调用gRPC方法成功, ProdStock = 100

5.目录结构

在这里插入图片描述

新版Go插件

1.官方提醒新版使用以下方式生成

直接在模块根目录执行即可

protoc --go_out=./service --go-grpc_out=./service pb\product.proto

2.生成后目录结构发生改变

在这里插入图片描述

3.直接运行代码会报错

.\main.go:81:50: cannot use service.ProductService (variable of type *service.productService) as service.ProductServiceServer value in argument to service.RegisterProductServiceServer: *service.productService does not implement service.ProductServiceServer (missing method mustEmbedUnimplementedProductServiceServer)

4.解决办法

product.go文件中使 productService 实现mustEmbedUnimplementedProductServiceServer方法

package service

import (
	"context"
)

var ProductService = &productService{}

type productService struct {
}

// GetProductStock 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
	stock := p.GetStockById(request.ProdId)
	return &ProductResponse{ProdStock: stock}, nil
}

func (p *productService) GetStockById(id int32) int32 {
	return 100
}

func (*productService) mustEmbedUnimplementedProductServiceServer() {}

 UnimplementedProductServiceServer must be embedded to have forward compatible implementations.
//type UnimplementedProductServiceServer struct {
//}
//
//func (UnimplementedProductServiceServer) GetProductStock(context.Context, *ProductRequest) (*ProductResponse, error) {
//	return nil, status.Errorf(codes.Unimplemented, "method GetProductStock not implemented")
//}
//func (UnimplementedProductServiceServer) mustEmbedUnimplementedProductServiceServer() {}

5.同样将生成的目录复制到客户端

在这里插入图片描述

6.使用命令行启动服务端与客户端

客户端执行结果:
证书读取成功
ca证书读取成功
设置TLS的TransportCredentials选项成功
证书认证通过
调用gRPC方法成功, ProdStock = 100

7.最终目录结构

在这里插入图片描述

修改mod文件,规范化模块名为:module test.com/test_grpc

go.mod文件

module test.com/test_grpc

go 1.20

require (
	google.golang.org/grpc v1.53.0
	google.golang.org/protobuf v1.28.1
)

require (
	github.com/golang/protobuf v1.5.2 // indirect
	golang.org/x/net v0.5.0 // indirect
	golang.org/x/sys v0.4.0 // indirect
	golang.org/x/text v0.6.0 // indirect
	google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
)

自动更新引用即可

更新proto文件

user.proto

syntax = "proto3";

option go_package="../service";
package service;

message User {
  string name = 1;
  int32  age  = 2;
  optional string password = 3; // optional代表指针
  optional string address = 4;
}

product.proto

syntax = "proto3";

// 从执行protoc这个命令的当前目录开始引入,如果在user.proto文件同级目录,则`import "user.proto";`即可
import "pb/user.proto";

option go_package="../service";

package service; // 将要生成的go文件包名

message ProductRequest {
  int32 prod_id = 1; // 1代表顺序
}

message ProductResponse {
  int32 prod_stock = 1; // 1代表顺序
  User  user = 2; // 导入其他pb文件
}

service ProductService {
  rpc GetProductStock(ProductRequest) returns (ProductResponse);
}

重新生成Go代码:protoc --go_out=./service --go-grpc_out=./service pb\*.proto
*.proto代表该目录下的所有proto文件

google.protobuf.Any与anypb.New()的使用

message Content { // 定义新结构
  string msg = 1;
}

message ProductResponse {
  int32 prod_stock = 1; // 1代表顺序
  User  user = 2;
  google.protobuf.Any data = 3; // Any 类型
}
// GetProductStock 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
	stock := p.GetStockById(request.ProdId)
	addr := "beijing"
	user := &User{Address: &addr}
	
	a, _ := anypb.New(&Content{Msg: "hello"}) // 转换成Any类型
	return &ProductResponse{ProdStock: stock, User: user,Data: a}, nil

}

Stream

使用stream关键字修饰来表示流程传输。当该关键字修饰参数时,表示为客户端流式的gRPC接口;

普通RPC

rpc SimplePing(PingRequest) returns (PingReply);

客户端流式RPC

rpc SimplePing(stream PingRequest) returns (PingReply);

1.修改product.proto文件,重新生成Go文件,复制到客户端

syntax = "proto3";

// 从执行protoc这个命令的当前目录开始引入
import "pbfile/user.proto";
import "google/protobuf/any.proto"; //引入Any类型
option go_package="../service";

package service; // 将要生成的go文件包名

message ProductRequest {
  int32 prod_id = 1; // 1代表顺序
}

message Content {
  string msg = 1;
}

message ProductResponse {
  int32 prod_stock = 1; // 1代表顺序
  User  user = 2;
  google.protobuf.Any data = 3;
}
// 导入其他pb文件


service ProductService {
  rpc GetProductStock(ProductRequest) returns (ProductResponse);
  // 增加客户端流定义方法
  rpc UpdateProductStockClientStream(stream ProductRequest) returns(ProductResponse);
}

2.修改服务端product.go文件

package service

import (
	"context"
	"fmt"
	"google.golang.org/protobuf/types/known/anypb"
	"io"
)

var ProductService = &productService{}

type productService struct {
}

// GetProductStock 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
	stock := p.GetStockById(request.ProdId)
	addr := "beijing"
	user := &User{Address: &addr}
	a, _ := anypb.New(&Content{Msg: "hello"}) // 转换成Any类型
	return &ProductResponse{ProdStock: stock, User: user, Data: a}, nil
}

func (p *productService) GetStockById(id int32) int32 {
	return 100
}

func (*productService) mustEmbedUnimplementedProductServiceServer() {}

func (*productService) UpdateProductStockClientStream(stream ProductService_UpdateProductStockClientStreamServer) error {
	count := 0
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("服务端接收到的流", recv.ProdId)
		count++
		if count > 10 {
			rsq := &ProductResponse{ProdStock: recv.ProdId}
			err := stream.SendAndClose(rsq) // 发送响应
			if err != nil {
				return err
			}
			return nil
		}
	}
}

 UnimplementedProductServiceServer must be embedded to have forward compatible implementations.
//type UnimplementedProductServiceServer struct {
//}
//
//func (UnimplementedProductServiceServer) GetProductStock(context.Context, *ProductRequest) (*ProductResponse, error) {
//	return nil, status.Errorf(codes.Unimplemented, "method GetProductStock not implemented")
//}
//func (UnimplementedProductServiceServer) mustEmbedUnimplementedProductServiceServer() {}

3.修改客户端文件

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
	"test.com/test_grpc/client/auth"
	"test.com/test_grpc/client/service"
	"time"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.test.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	// resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	// if err != nil {
	// 	log.Fatal("调用gRPC方法失败: ", err)
	// }

	// fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)

	// 获取流
	stream, err := productServiceClient.UpdateProductStockClientStream(context.Background())
	if err != nil {
		log.Fatal("获取流失败", err)
	}

	// 定义切片,设置请求
	rsq := make(chan struct{}, 1)
	go prodRequest(stream, rsq)

	// 等待数据接收
	select {
	case <-rsq:
		// 接收数据
		recv, err := stream.CloseAndRecv()
		if err != nil {
			log.Fatal(err)
		}

		stock := recv.ProdStock
		fmt.Println("客户端收到响应: ", stock)
	}
}

// 请求接口
func prodRequest(stream service.ProductService_UpdateProductStockClientStreamClient, rsq chan struct{}) {
	count := 0
	for {
		request := &service.ProductRequest{
			ProdId: 100,
		}

		// 发送数据
		err := stream.SendMsg(request)
		if err != nil {
			log.Fatal(err)
		}

		time.Sleep(time.Second)
		count++
		if count > 10 {
			rsq <- struct{}{}
			break
		}
	}
}

4.执行结果

客户端:
证书读取成功
ca证书读取成功
设置TLS的TransportCredentials选项成功
证书认证通过
客户端收到响应: 100

服务端流式RPC

rpc SimplePing(PingRequest) returns (stream PingReply);

1.修改product.proto文件,重新生成Go文件,复制到客户端

syntax = "proto3";

// 从执行protoc这个命令的当前目录开始引入
import "pbfile/user.proto";
import "google/protobuf/any.proto"; //引入Any类型
option go_package="../service";

package service; // 将要生成的go文件包名

message ProductRequest {
  int32 prod_id = 1; // 1代表顺序
}

message Content {
  string msg = 1;
}

message ProductResponse {
  int32 prod_stock = 1; // 1代表顺序
  User  user = 2;
  google.protobuf.Any data = 3;
}
// 导入其他pb文件


service ProductService {
  rpc GetProductStock(ProductRequest) returns (ProductResponse);
  // 增加客户端流定义方法
  rpc UpdateProductStockClientStream(stream ProductRequest) returns(ProductResponse);

  // 服务端流定义方法
  rpc GetProductStockServerStream(ProductRequest) returns(stream ProductResponse);
}

2.修改服务端product.go文件

package service

import (
	"context"
	"fmt"
	"google.golang.org/protobuf/types/known/anypb"
	"io"
	"time"
)

var ProductService = &productService{}

type productService struct {
}

// GetProductStock 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
	stock := p.GetStockById(request.ProdId)
	addr := "beijing"
	user := &User{Address: &addr}
	a, _ := anypb.New(&Content{Msg: "hello"}) // 转换成Any类型
	return &ProductResponse{ProdStock: stock, User: user, Data: a}, nil
}

func (p *productService) GetStockById(id int32) int32 {
	return 100
}

func (*productService) mustEmbedUnimplementedProductServiceServer() {}

func (*productService) UpdateProductStockClientStream(stream ProductService_UpdateProductStockClientStreamServer) error {
	count := 0
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("服务端接收到的流", recv.ProdId)
		count++
		if count > 10 {
			rsq := &ProductResponse{ProdStock: recv.ProdId}
			err := stream.SendAndClose(rsq) // 发送响应
			if err != nil {
				return err
			}
			return nil
		}
	}
}

// GetProductStockServerStream 新增服务端接口实现
func (p *productService) GetProductStockServerStream(request *ProductRequest, stream ProductService_GetProductStockServerStreamServer) error {
	count := 0
	for {
		rsp := &ProductResponse{ProdStock: request.ProdId}
		err := stream.SendMsg(rsp)
		if err != nil {
			return err
		}
		time.Sleep(time.Second)
		count++
		if count > 10 {
			return nil
		}
	}
}

 UnimplementedProductServiceServer must be embedded to have forward compatible implementations.
//type UnimplementedProductServiceServer struct {
//}
//
//func (UnimplementedProductServiceServer) GetProductStock(context.Context, *ProductRequest) (*ProductResponse, error) {
//	return nil, status.Errorf(codes.Unimplemented, "method GetProductStock not implemented")
//}
//func (UnimplementedProductServiceServer) mustEmbedUnimplementedProductServiceServer() {}

3.修改客户端文件

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io"
	"io/ioutil"
	"log"
	"test.com/test_grpc/client/auth"
	"test.com/test_grpc/client/service"
	"time"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.test.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	// resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	// if err != nil {
	// 	log.Fatal("调用gRPC方法失败: ", err)
	// }

	// fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)

	// 获取流
	// stream, err := productServiceClient.UpdateProductStockClientStream(context.Background())
	// if err != nil {
	// 	log.Fatal("获取流失败", err)
	// }

	// rsq := make(chan struct{}, 1)
	// go prodRequest(stream, rsq)
	// select {
	// case <-rsq:
	// 	recv, err := stream.CloseAndRecv()
	// 	if err != nil {
	// 		log.Fatal(err)
	// 	}

	// 	stock := recv.ProdStock
	// 	fmt.Println("客户端收到响应: ", stock)
	// }

	request := &service.ProductRequest{
		ProdId: 100,
	}

	// 调用服务端接口获取流
	stream, err := productServiceClient.GetProductStockServerStream(context.Background(), request)
	if err != nil {
		log.Fatal("获取流失败")
	}

	// 循环获取流
	for {
		recv, err := stream.Recv()
		if err != nil {
			// 流数据接收完成标志
			if err == io.EOF {
				fmt.Println("客户端接收数据完成")
				stream.CloseSend()
				break
			}

			log.Fatal(err)
		}
		fmt.Println("客户端收到的流", recv.ProdStock)
	}
}

func prodRequest(stream service.ProductService_UpdateProductStockClientStreamClient, rsq chan struct{}) {
	count := 0
	for {
		request := &service.ProductRequest{
			ProdId: 100,
		}

		err := stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}

		time.Sleep(time.Second)
		count++
		if count > 10 {
			rsq <- struct{}{}
			break
		}
	}
}

4.执行结果

客户端:
证书读取成功
ca证书读取成功
设置TLS的TransportCredentials选项成功
证书认证通过
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端收到的流 100
客户端接收数据完成

双向流式RPC

// 双向流式RPC
rpc SimplePing(stream PingRequest) returns (stream PingReply);

1.修改product.proto文件,重新生成Go文件,复制到客户端

syntax = "proto3";

// 从执行protoc这个命令的当前目录开始引入
import "pbfile/user.proto";
import "google/protobuf/any.proto"; //引入Any类型
option go_package="../service";

package service; // 将要生成的go文件包名

message ProductRequest {
  int32 prod_id = 1; // 1代表顺序
}

message Content {
  string msg = 1;
}

message ProductResponse {
  int32 prod_stock = 1; // 1代表顺序
  User  user = 2;
  google.protobuf.Any data = 3;
}
// 导入其他pb文件


service ProductService {
  rpc GetProductStock(ProductRequest) returns (ProductResponse);

  // 增加客户端流定义方法
  rpc UpdateProductStockClientStream(stream ProductRequest) returns(ProductResponse);

  // 服务端流定义方法
  rpc GetProductStockServerStream(ProductRequest) returns(stream ProductResponse);

  // 双向流定义方法
  rpc SayHelloStream(stream ProductRequest) returns(stream ProductResponse);
}

2.修改服务端product.go文件

package service

import (
	"context"
	"fmt"
	"google.golang.org/protobuf/types/known/anypb"
	"io"
	"time"
)

var ProductService = &productService{}

type productService struct {
}

// GetProductStock 接口实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
	stock := p.GetStockById(request.ProdId)
	addr := "beijing"
	user := &User{Address: &addr}
	a, _ := anypb.New(&Content{Msg: "hello"}) // 转换成Any类型
	return &ProductResponse{ProdStock: stock, User: user, Data: a}, nil
}

func (p *productService) GetStockById(id int32) int32 {
	return 100
}

func (*productService) mustEmbedUnimplementedProductServiceServer() {}

func (*productService) UpdateProductStockClientStream(stream ProductService_UpdateProductStockClientStreamServer) error {
	count := 0
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("服务端接收到的流", recv.ProdId)
		count++
		if count > 10 {
			rsq := &ProductResponse{ProdStock: recv.ProdId}
			err := stream.SendAndClose(rsq) // 发送响应
			if err != nil {
				return err
			}
			return nil
		}
	}
}

// GetProductStockServerStream 新增服务端接口实现
func (p *productService) GetProductStockServerStream(request *ProductRequest, stream ProductService_GetProductStockServerStreamServer) error {
	count := 0
	for {
		rsp := &ProductResponse{ProdStock: request.ProdId}
		err := stream.SendMsg(rsp)
		if err != nil {
			return err
		}
		time.Sleep(time.Second)
		count++
		if count > 10 {
			return nil
		}
	}
}

func (p *productService) SayHelloStream(stream ProductService_SayHelloStreamServer) error {
	for {
		// 接收消息
		recv, err := stream.Recv()
		if err != nil {
			return nil
		}

		fmt.Println("服务端接收客户端的消息", recv.ProdId)
		time.Sleep(time.Second)

		// 发送消息
		rsp := &ProductResponse{ProdStock: recv.ProdId}
		err = stream.Send(rsp)
		if err != nil {
			return err
		}
	}
}

 UnimplementedProductServiceServer must be embedded to have forward compatible implementations.
//type UnimplementedProductServiceServer struct {
//}
//
//func (UnimplementedProductServiceServer) GetProductStock(context.Context, *ProductRequest) (*ProductResponse, error) {
//	return nil, status.Errorf(codes.Unimplemented, "method GetProductStock not implemented")
//}
//func (UnimplementedProductServiceServer) mustEmbedUnimplementedProductServiceServer() {}

3.修改客户端文件

package main

import (
	"google.golang.org/grpc"

	"log"

	"test.com/test_grpc/client/service"

	"context"

	"fmt"

	"google.golang.org/grpc/credentials"

	"crypto/tls"

	"crypto/x509"

	"io/ioutil"

	//	"io"

	"test.com/test_grpc/client/auth"

	"time"
)

func main() {
	// 添加公钥
	// creds, err0 := credentials.NewClientTLSFromFile("../cert/server.pem", "*.test.com")
	// if err0 != nil {
	// 	log.Fatal("证书错误: ", err0)
	// }

	// 证书认证-双向认证
	// 从证书相关文件中读取解析信息, 得到证书公钥、密钥对
	cert, err0 := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
	if err0 != nil {
		log.Fatal("证书读取失败", err0)
	}

	fmt.Println("证书读取成功")

	// 创建一个新的、空的CertPool
	certPool := x509.NewCertPool()
	ca, err1 := ioutil.ReadFile("../cert/ca.crt")
	if err1 != nil {
		log.Fatal("ca证书读取失败", err1)
	}

	fmt.Println("ca证书读取成功")

	// 尝试解析所传入的PEM编码的证书,如果解析成功会将其加到CertPool中,便于后面使用
	certPool.AppendCertsFromPEM(ca)
	// 构建基于TLS的TransportCredentials选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链, 允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		ServerName:   "*.test.com",
		RootCAs:      certPool,
	})

	fmt.Println("设置TLS的TransportCredentials选项成功")

	token := &auth.Authentication{
		User:   "admin",
		Passwd: "admin@123",
	}

	// 创建连接
	conn, err := grpc.Dial(":8888", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(token))
	if err != nil {
		log.Fatal("服务端连接失败: ", err)
	}

	fmt.Println("证书认证通过")

	// 退出时关闭连接
	defer conn.Close()

	// 创建客户端实例
	productServiceClient := service.NewProductServiceClient(conn)

	// 方法请求
	// resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
	// if err != nil {
	// 	log.Fatal("调用gRPC方法失败: ", err)
	// }

	// fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)

	// 获取流
	// stream, err := productServiceClient.UpdateProductStockClientStream(context.Background())
	// if err != nil {
	// 	log.Fatal("获取流失败", err)
	// }

	// rsq := make(chan struct{}, 1)
	// go prodRequest(stream, rsq)
	// select {
	// case <-rsq:
	// 	recv, err := stream.CloseAndRecv()
	// 	if err != nil {
	// 		log.Fatal(err)
	// 	}

	// 	stock := recv.ProdStock
	// 	fmt.Println("客户端收到响应: ", stock)
	// }

	// request := &service.ProductRequest{
	// 	ProdId: 100,
	// }

	// stream, err := productServiceClient.GetProductStockServerStream(context.Background(), request)
	// if err != nil {
	// 	log.Fatal("获取流失败")
	// }

	// for {
	// 	recv, err := stream.Recv()
	// 	if err != nil {
	// 		if err == io.EOF {
	// 			fmt.Println("客户端接收数据完成")
	// 			stream.CloseSend()
	// 			break
	// 		}

	// 		log.Fatal(err)
	// 	}
	// 	fmt.Println("客户端收到的流", recv.ProdStock)
	// }

	// 获取双向流
	stream, err := productServiceClient.SayHelloStream(context.Background())

	for {
		// 发送消息
		request := &service.ProductRequest{
			ProdId: 100,
		}
		err = stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}
		time.Sleep(time.Second)
		recv, err := stream.Recv()
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("客户端接收服务端的消息", recv.ProdStock)
	}
}

func prodRequest(stream service.ProductService_UpdateProductStockClientStreamClient, rsq chan struct{}) {
	count := 0
	for {
		request := &service.ProductRequest{
			ProdId: 100,
		}

		err := stream.Send(request)
		if err != nil {
			log.Fatal(err)
		}

		time.Sleep(time.Second)
		count++
		if count > 10 {
			rsq <- struct{}{}
			break
		}
	}
}

4.执行结果

客户端与服务端互相发数据

纠正

proto文件生成后,拷贝*.pb.go文件到客户端即可

Reference

https://www.bilibili.com/video/BV16Z4y117yz

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/339096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习HandlerThread

HandlerThread是一个扩展了Thread的类。也就意味着它和普通的Thread类的调用没有什么区别&#xff0c;仍然要调用start()。 如上图所示&#xff0c;扩展后的HandlerThread类有一个Looper和Handler。 关于这一块的知识可以参考一下《关于Handler我们应该知道的知识》 HandlerTh…

普冉PY32系列(五) 使用JLink RTT代替串口输出日志

目录 普冉PY32系列(一) PY32F0系列32位Cortex M0 MCU简介普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单普冉PY32系列(四) PY32F002A/003/030的时钟设置普冉PY32系列(五) 使用JLink RTT代替串口输出日志 JLink …

Python-第五天 Python函数

Python-第五天 Python函数一、函数介绍1. 什么事函数二、函数的定义1.函数的定义&#xff1a;2.案例三、函数的参数1.函数的传入参数2.案例升级四、函数的返回值1.什么是返回值2.返回值的语法3.None类型4.None类型的应用场景五、函数说明文档1.函数的说明文档2.在PyCharm中查看…

脑机接口协议V1.0

脑机接口&#xff0c;有时也称作“大脑端口”direct neural interface或者“脑机融合感知”brain-machine interface&#xff0c;它是在人或动物脑&#xff08;或者脑细胞的培养物&#xff09;与外部设备间建立的直接连接通路。在单向脑机接口的情况下&#xff0c;计算机或者接…

leaflet显示高程

很多地图软件都能随鼠标移动动态显示高程。这里介绍一种方法&#xff0c;我所得出的。1 下载高程数据一般有12.5m数据下载&#xff0c;可惜精度根本不够&#xff0c;比如mapbox的免费在线的&#xff0c;或者91卫图提供百度网盘打包下载的&#xff0c;没法用&#xff0c;差距太大…

记录robosense RS-LIDAR-16使用过程3

一、wireshark抓包保存pcap文件并解析ubuntu18安装wireshark&#xff0c;参考下面csdn教程&#xff0c;官网教程我看的一脸蒙&#xff08;可能英语太差&#xff09;https://blog.csdn.net/weixin_46048542/article/details/121730448?spm1001.2101.3001.6650.2&utm_medium…

秒杀项目之服务调用分布式session

目录 nginx动静分离 服务调用 创建配置zmall-cart购物车模块 创建配置zmall-order订单模块 服务调用 spring session实战 什么是Spring Session 为什么要使用Spring Session 错误案例展示 配置spring-session 二级域名问题 用户登录 nginx动静分离 第1步&#xff…

如何用演示程序检测K100|K720|K750电动发卡读写一体机性能

K100|K720|K750电动发卡读写一体机采用工业级设 计&#xff0c;表面烤漆处理&#xff0c;具有良好的耐磨耐腐蚀性。适应各种高、低温&#xff0c; 多灰尘等 恶劣环境。其发卡原理为拟人型摩擦式发卡&#xff0c;对各类变形卡有非常好的适应性。 CNC的精度保证卡距的绝对一致性、…

Filter过滤器完成验证代码的封装

Filter过滤器完成验证代码的封装filter是什么1 使用filter2 filter配置到项目中验证用户权限是需要反复使用的代码块&#xff0c;把他封装到filter中&#xff0c;减少代码冗余filter是什么 init()方法&#xff1a;初始化方法&#xff0c;在创建Filter后立即调用。可用于完成初始…

刚刚,体验了一把Bing chat很爽

文章目录刚刚&#xff0c;体验了一把Bing chat很爽你能做啥&#xff1f;与chatgpt有什么不同&#xff1f;以下是Bingchat的 10个新功能1⃣️在网上搜索结果2⃣️摘要链接3⃣️对话助手4⃣️向您发送实际信息&#xff0c;正确的链接5⃣️在单个查询中执行多个搜索6⃣️玩冒险游戏…

内网渗透(十八)之Windows协议认证和密码抓取-本地认证(NTML哈希和LM哈希)

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

【Web测试】各类web控件测试点汇总,软测人必备

一 、界面检查 进入一个页面测试&#xff0c;首先是检查title&#xff0c;页面排版&#xff0c;字段等&#xff0c;而不是马上进入文本框校验 1、页面名称title是否正确 2、当前位置是否可见 您的位置&#xff1a;xxx>xxxx 3、文字格式统一性 4、排版是否整齐 5、列表项显示…

leetcode: 3Sum

leetcode: 3Sum1. 题目描述2. 思考3. 解题3. 总结1. 题目描述 Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i ! j, i ! k, and j ! k, and nums[i] nums[j] nums[k] 0. Notice that the solution set must not contain …

爬虫Python入门好学吗?学什么?

爬虫Python入门好学吗&#xff1f;学爬虫需要具备一定的基础&#xff0c;有编程基础学Python爬虫更容易学。但要多看多练&#xff0c;有自己的逻辑想法。用Python达到自己的学习目的才算有价值。如果是入门学习了解&#xff0c;开始学习不难&#xff0c;但深入学习有难度&#…

2022年12月电子学会Python等级考试试卷(一级)答案解析

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;一级&#xff09; 一、单选题(共25题&#xff0c;共50分) 1. 关于Python语言的注释&#xff0c;以下选项中描述错误的是&#xff1f;&#xff08; &#xff09; A. Python语言有两种注释方式&…

JavaEE16-Spring事务和事务传播机制

目录 1.为什么需要事务&#xff1f; 2.MySQL中事务的使用 3.Spring中事务的实现 3.1.编程式事务&#xff08;手动写代码操作事务&#xff09; 3.2.声明式事务&#xff08;利用注解自动开启和提交事务&#xff09;(使用为主) 3.2.1.Transactional作用范围 3.2.2.Transact…

JetpackCompose从入门到实战学习笔记8—ConstraintLayout的简单使用

JetpackCompose从入门到实战学习笔记8—ConstraintLayout的简单使用 1.简介&#xff1a; Compose 中的 ConstraintLayout ConstraintLayout 是一种布局&#xff0c;让您可以相对于屏幕上的其他可组合项来放置可组合项。它是一种实用的替代方案&#xff0c;可代替使用多个已嵌…

JVM垃圾回收机制GC理解

目录JVM垃圾回收分代收集如何识别垃圾引用计数法可达性分析法引用关系四种类型&#xff1a; 强、软、弱、虚强引用软引用 SoftReference弱引用 WeakReferenceWeakHashMap软引用与虚引用的使用场景虚引用与引用队列引用队列虚引用 PhantomReference垃圾回收算法引用计数复制 Cop…

06- 梯度下降(SGDRegressor) (机器学习)

梯度下降算法也是先构建误差值的函数, 通过求误差值函数的最小值来达到误差最小的目的, 不过梯度下降是先随机取值, 然后求函数在该点的导数, 如果导数为正, 下一次取值向反方向移动, 如果导数为负, 正向移动, 直到导数取值极小时, 认定误差达到一个可以接受的范围, 然后导出相…

解读手机拍照的各个参数(拍照时,上面会有6个符号)

1第一个符号是闪光灯符号&#xff0c;如下图所示。有四种模式&#xff0c; 手机的闪光灯分别为关闭、自动、开启和常亮四种状态。 关闭&#xff1a;就是在任何情况下都不会闪光 自动&#xff1a;由手机来判断此时的光线强弱&#xff0c;若手机测光认为光线太弱&#xff0c;则…