gRPC的简单应用
创始人
2024-05-25 12:28:51
0

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.protopackage serviceimport (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.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsProdId 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.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsProdStock 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.FileDescriptorvar 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.Oncefile_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.ProductRequest1, // 1: service.ProductService.GetProductStock:output_type -> service.ProductResponse1, // [1:2] is the sub-list for method output_type0, // [0:1] is the sub-list for method input_type0, // [0:0] is the sub-list for extension type_name0, // [0:0] is the sub-list for extension extendee0, // [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.statecase 1:return &v.sizeCachecase 2:return &v.unknownFieldsdefault:return nil}}file_product_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {switch v := v.(*ProductResponse); i {case 0:return &v.statecase 1:return &v.sizeCachecase 2:return &v.unknownFieldsdefault: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.Filefile_product_proto_rawDesc = nilfile_product_proto_goTypes = nilfile_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 serviceimport "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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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.UnaryServerInterceptorauthInterceptor = func(ctx context.Context,req interface{},info *grpc.UnaryServerInfo,handler grpc.UnaryHandler,) (resp interface{}, err error) {// 拦截请求, 验证tokenerr = 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 stringvar passwd stringif 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 authimport "context"type Authentication struct {User   stringPasswd 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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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 serviceimport ("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_grpcgo 1.20require (google.golang.org/grpc v1.53.0google.golang.org/protobuf v1.28.1
)require (github.com/golang/protobuf v1.5.2 // indirectgolang.org/x/net v0.5.0 // indirectgolang.org/x/sys v0.4.0 // indirectgolang.org/x/text v0.6.0 // indirectgoogle.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 serviceimport ("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 := 0for {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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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.ProdStockfmt.Println("客户端收到响应: ", stock)}
}// 请求接口
func prodRequest(stream service.ProductService_UpdateProductStockClientStreamClient, rsq chan struct{}) {count := 0for {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 serviceimport ("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 := 0for {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 := 0for {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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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 := 0for {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 serviceimport ("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 := 0for {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 := 0for {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 mainimport ("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("证书读取成功")// 创建一个新的、空的CertPoolcertPool := 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 := 0for {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

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...