前言

特别是在微服务架构中

  • gRPC用于内部服务间通信
  • Gin用于对外提供RESTful API。

单体架构弊端

  • 一旦某个服务宕机,会引起整个应用不可用,隔离性差
  • 只能整体应用进行伸缩,浪费服务器资源,可伸缩性差
  • 代码耦合在一起,可维护性差

微服务架构

  • 解决了单体架构的弊端

微服务架构同时引入了新的问题

  • 代码冗余,例如微服务中多个应用都需要Web框架代码
  • 提高了应用部署、运维、可观测复杂程度需借助云原生架构
  • 微服务架构中服务之间调用关系复杂、多变,需要动态发现和高性能RPC框架

微服务带火了RPC

随着微服务架构的兴起,单体应用被拆分成微服务架构,多个微服务应用通常部署在多个不同的主机之上,被网络隔开。

不同微服务应用间想要交互通信,需要跨服务器/主机发起网络调用,做不到之前单体应用那样,在本地调用函数一样简单了。

提起网络调用,我们能立马想到就是HTTP协议,但是当前HTTP协议大多使用HTTP/1.X版本;

RPC需求催生gRPC

在微服务架构中,HTTP虽然便捷方便,但性能较低;

这时候就需要引入RPC(远程过程调用),通过自定义协议发起TCP调用,来加快传输效率。

RPC框架使得我们能在云原生架构中, 更容易地创建分布式应用和服务,go-zero微服务框架底层封装了gRPC;

gRPC一开始是由Google开发,是1款语言中立、平台中立、开源的远程过程调用(RPC)系统。

gRPC系统是基于HTTP/2协议封装的RPC框架。

gRPC默认使用protocol buffers序列化和反序列化机制,当然也可以使用其他数据格式如JSON。

gRPC 客户端和服务端可以在多种环境中运行和交互 - 从Google内部的服务器到本地笔记本,并且可以用任何 gRPC 支持的语言来编写。

可以很容易地用Java 创建1个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。

Google 最新 API 将有 gRPC 版本的接口,使你很容易地将 Google 的功能集成到你的应用里。

 

一、HTTP/2协议

HTTP/2 是 HTTP 协议的第二个主要版本,它在 HTTP/1.x 的基础上引入了许多改进,以提高网络性能和效率。

以下是 HTTP/2 与 HTTP/1.x 之间的一些主要区别

二进制协议

  • HTTP/2:采用二进制格式,而不是 HTTP/1.x 的文本格式。这使得解析更快、更高效。
  • HTTP/1.x:基于文本的协议,头部和消息体都是以文本形式传输。

多路复用

  • HTTP/2:支持多路复用,即在单一的 TCP 连接上同时发送多个请求和响应,而不需要等待前一个请求完成。
  • HTTP/1.x:每个请求/响应都需要自己的 TCP 连接,或者在 HTTP/1.1 中使用持久连接(Connectionkeep-alive),但仍然需要按顺序发送请求和接收响应。

头部压缩

  • HTTP/2:引入了 HPACK 压缩算法,对请求和响应的头部进行压缩,减少了冗余头部信息的传输。
  • HTTP/1.x:头部信息未经压缩,可能导致大量的重复数据传输。

服务器推送

  • HTTP/2:服务器可以主动向客户端推送资源,而不需要客户端明确请求这些资源。
  • HTTP/1.x:服务器不能主动推送资源,除非客户端请求。

流控制

  • HTTP/2:使用流控制机制来防止数据过载,允许接收方控制发送方的数据流量。
  • HTTP/1.x:没有内建的流控制机制。

优先级

  • HTTP/2:允许客户端为请求设置优先级,这样服务器可以优先处理更重要的请求。
  • HTTP/1.x:没有内建的请求优先级机制。

安全性

  • HTTP/2:设计时考虑了与 TLS/SSL 的兼容性,虽然不是强制性的,但推荐使用 HTTPS。
  • HTTP/1.x:也可以通过 HTTPS 使用 TLS/SSL,但在 HTTP/2 中安全性更加突出。

性能

  • HTTP/2:由于上述特性,HTTP/2 通常比 HTTP/1.x 提供更好的性能,尤其是在高延迟网络环境中。

兼容性

  • HTTP/2:设计时考虑了向后兼容性,但某些旧的 HTTP/1.x 特性(如管道)在 HTTP/2 中不再支持。
  • HTTP/1.x:广泛支持,但性能和效率不如 HTTP/2。总的来说,HTTP/2 旨在解决 HTTP/1.x 在性能和效率方面的一些限制,特别是在高并发和高延迟的网络环境中

 

二、gRPC框架

gRPC框架拥有以下优势

与许多RPC系统类似,gRPC也是基于以下理念:

  • 定义1个服务,指定其能够被远程调用的方法(包含参数和返回类型)
  • 服务端实现这个接口,运行1个gRPC服务器来处理客户端调用
  • 客户端应用程序通过存根(Stub)将参数编码成二进制并通过HTTP/2传输发送给服务器。存根提供了与服务端相同的方法,客户端可以调用这些方法,存根会将调用的参数包装在适当的Protocol Buffer消息类型中,然后将请求发送到服务器,并返回服务器的Protocol Buffer响应。

RPC调用流程

 下图为1个完成的RPC调用流程:

准备gRPC开发环境

1.安装protoc工具

protoc是Google Protocol Buffers的编译器,它用于将.proto文件编译成各种编程语言的代码。

protoc支持多种语言,包括C++、Java、Python、Go等,可以直接从.proto文件生成对应语言的数据结构代码。

下载地址:https://github.com/protocolbuffers/protobuf/releases

这里下载protoc-3.19.1-win64.zip:

https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-win64.zip
614345/article/details/131860694

2.安装protoc-gen-go插件

protoc-gen-go是protoc的1个插件,专门用于生成Go语言的代码。

go install github.com/golang/protobuf/protoc-gen-go@latest

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

3.hello.proto

// 指定proto版本
syntax = "proto3";
// 指定包名
//package mypackage;

/* 指定文件生成的路径和包名
 ./hello为路径
 service为生成的包名
 如果指定的go_package="./hello";则包名和路径同名(如果路径有多层,则包名和路径的最后一层相同)
  */
option go_package = "./hello;service";

// 定义Hello服务
service Hello {
  // 定义SayHello方法
  rpc SayHello(HelloRequest) returns (HelloReply) {}
}

// HelloRequest 请求结构
message HelloRequest {
  string name = 1;
}

// HelloReply 响应结构
message HelloReply {
  string message = 1;
}

4.生成hello.pb.go和hello_grpc.pb.go文件

hello.pb.go    负责处理 Protocol Buffers 的数据结构;

// 指定proto版本

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//     protoc-gen-go v1.35.1
//     protoc        v3.19.1
// source: hello.proto

package service

import (
    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)
)

// HelloRequest 请求结构
type HelloRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

func (x *HelloRequest) Reset() {
    *x = HelloRequest{}
    mi := &file_hello_proto_msgTypes[0]
    ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
    ms.StoreMessageInfo(mi)
}

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

func (*HelloRequest) ProtoMessage() {}

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

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

func (x *HelloRequest) GetName() string {
    if x != nil {
        return x.Name
    }
    return ""
}

// HelloReply 响应结构
type HelloReply struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}

func (x *HelloReply) Reset() {
    *x = HelloReply{}
    mi := &file_hello_proto_msgTypes[1]
    ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
    ms.StoreMessageInfo(mi)
}

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

func (*HelloReply) ProtoMessage() {}

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

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

func (x *HelloReply) GetMessage() string {
    if x != nil {
        return x.Message
    }
    return ""
}

var File_hello_proto protoreflect.FileDescriptor

var file_hello_proto_rawDesc = []byte{
    0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a,
    0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
    0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
    0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12,
    0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
    0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x31, 0x0a, 0x05, 0x48, 0x65, 0x6c,
    0x6c, 0x6f, 0x12, 0x28, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0d,
    0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e,
    0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f,
    0x2e, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62,
    0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
    file_hello_proto_rawDescOnce sync.Once
    file_hello_proto_rawDescData = file_hello_proto_rawDesc
)

func file_hello_proto_rawDescGZIP() []byte {
    file_hello_proto_rawDescOnce.Do(func() {
        file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData)
    })
    return file_hello_proto_rawDescData
}

var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_hello_proto_goTypes = []any{
    (*HelloRequest)(nil), // 0: HelloRequest
    (*HelloReply)(nil),   // 1: HelloReply
}
var file_hello_proto_depIdxs = []int32{
    0, // 0: Hello.SayHello:input_type -> HelloRequest
    1, // 1: Hello.SayHello:output_type -> HelloReply
    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_hello_proto_init() }
func file_hello_proto_init() {
    if File_hello_proto != nil {
        return
    }
    type x struct{}
    out := protoimpl.TypeBuilder{
        File: protoimpl.DescBuilder{
            GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
            RawDescriptor: file_hello_proto_rawDesc,
            NumEnums:      0,
            NumMessages:   2,
            NumExtensions: 0,
            NumServices:   1,
        },
        GoTypes:           file_hello_proto_goTypes,
        DependencyIndexes: file_hello_proto_depIdxs,
        MessageInfos:      file_hello_proto_msgTypes,
    }.Build()
    File_hello_proto = out.File
    file_hello_proto_rawDesc = nil
    file_hello_proto_goTypes = nil
    file_hello_proto_depIdxs = nil
}
hello.pb.go

hello_grpc.pb.go 负责处理 gRPC 服务的通信;

// 指定proto版本

// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc             v3.19.1
// source: hello.proto

package service

import (
    context "context"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
)

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9

const (
    Hello_SayHello_FullMethodName = "/Hello/SayHello"
)

// HelloClient is the client API for Hello service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// 定义Hello服务
type HelloClient interface {
    // 定义SayHello方法
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

type helloClient struct {
    cc grpc.ClientConnInterface
}

func NewHelloClient(cc grpc.ClientConnInterface) HelloClient {
    return &helloClient{cc}
}

func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
    out := new(HelloReply)
    err := c.cc.Invoke(ctx, Hello_SayHello_FullMethodName, in, out, cOpts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// HelloServer is the server API for Hello service.
// All implementations must embed UnimplementedHelloServer
// for forward compatibility.
//
// 定义Hello服务
type HelloServer interface {
    // 定义SayHello方法
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
    mustEmbedUnimplementedHelloServer()
}

// UnimplementedHelloServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedHelloServer struct{}

func (UnimplementedHelloServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
    return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedHelloServer) mustEmbedUnimplementedHelloServer() {}
func (UnimplementedHelloServer) testEmbeddedByValue()               {}

// UnsafeHelloServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to HelloServer will
// result in compilation errors.
type UnsafeHelloServer interface {
    mustEmbedUnimplementedHelloServer()
}

func RegisterHelloServer(s grpc.ServiceRegistrar, srv HelloServer) {
    // If the following call pancis, it indicates UnimplementedHelloServer was
    // embedded by pointer and is nil.  This will cause panics if an
    // unimplemented method is ever invoked, so we test this at initialization
    // time to prevent it from happening at runtime later due to I/O.
    if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
        t.testEmbeddedByValue()
    }
    s.RegisterService(&Hello_ServiceDesc, srv)
}

func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(HelloRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(HelloServer).SayHello(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: Hello_SayHello_FullMethodName,
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest))
    }
    return interceptor(ctx, in, info, handler)
}

// Hello_ServiceDesc is the grpc.ServiceDesc for Hello service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Hello_ServiceDesc = grpc.ServiceDesc{
    ServiceName: "Hello",
    HandlerType: (*HelloServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "SayHello",
            Handler:    _Hello_SayHello_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "hello.proto",
}
hello_grpc.pb.go

在实际开发中,您需要这两个文件来完整地实现 gRPC 服务的序列化、服务定义和通信逻辑;

protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto

5.服务端

package main

import (
    "context"
    "fmt"
    pd "gRPC-demo/gRPC-server/proto/hello"
    "google.golang.org/grpc"
    "net"
)

//继承hello_grpc.pb.go中的UnimplementedHelloServer结构体
type serverMsg struct {
    pd.UnimplementedHelloServer
}

//实现hello_grpc.pb.go中的接口
func (s *serverMsg) SayHello(contxt context.Context, request *pd.HelloRequest) (*pd.HelloReply, error) {
    return &pd.HelloReply{Message: "hello " + request.Name}, nil
}

func main() {
    //开启端口
    listen, _ := net.Listen("tcp", ":8001")
    //创建gRPC服务
    grpcServer := grpc.NewServer()
    //在gRPC服务端注册我们自己编写的服务
    pd.RegisterHelloServer(grpcServer, &serverMsg{})
    //启动服务
    err := grpcServer.Serve(listen)
    if err != nil {
        fmt.Println("服务端启动失败", err)
        return
    }
}
server.go

6.客户端

package main

import (
    "context"
    "fmt"
    pd "gRPC-demo/gRPC-client/proto/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        fmt.Println("连接错误", err)
    }
    defer conn.Close()
    //建立连接
    client := pd.NewHelloClient(conn)
    //执行RPC调用(这个方法在服务器来实现并返回结果
    response, _ := client.SayHello(context.Background(), &pd.HelloRequest{Name: "zhanggen"})
    fmt.Println(response.GetMessage())
}
client.go

三、gRPC安全认证 

开发了gRPC服务端和客户端之后,发现任何gRPC客户端只要知道gRPC服务端的IP和端口都可以和服务端建立连接,获取服务端数据;

gRPC框架支持以下安全认证机制,保证信息安全传输;

  • SSL/TSL认证方式(采用http2协议)
  • 基于Token的认证方式

1.SSL/TSL认证

key:服务器上的私钥文件,用于给Response内容加密,及Request内容解密
crt文件:由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者签名等信息
csr文件:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
pem文件:基于Base64编码的证书格式,扩展名包扩PEM、CRT和CER

以下步骤会生成8个文件

1.生成私钥
openssl genrsa -out server.key 2048
2.生成crt证书文件 全部回车即可,可以不填
openssl req -new -x509 -key server.key -out server.crt -days 36500
3.生成csr文件
openssl req -new -key server.key -out server.csr
4.更改openss1.cnf (Linux 是openss1.cfg)1
#1)复制一份你安装的openssl的bin目录里面的openssl.cnf 文件到你项目所在的目录下
#2)找到[CA_default],打开 copy_extensions = copy (就是把前面的#去掉)
#3)找到[req],打开req_extensions = v3_req # The extensions to add to a certificate request
#4)找到[v3_req],添加 subjectAltName = @alt_names
#5)添加新的标签[alt_names],和标签字段
[alt_names]
DNS.1 =*.zhanggen.com
5.生成证书私钥test.key
genpkey -algorithm RSA -out test.key

6.#通过私钥test.key生成证书请求文件test.csr(注意cfg和cnf)
openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomp/CN=myname" -config ./openssl.cfg -extensions v3_req
7.# test.csr是上面生成的证书请求文件。ca.crt/server.key是CA证书文件和key,用来对test.csr进行签名认证。这两个文件在第一部分生成。

8.# 生成SAN证书 pem
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req

2.使用SSL/TSL认证后的服务端和客户端

服务端

package main

import (
    "context"
    "fmt"
    pd "gRPC-demo/gRPC-server/proto/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "net"
)

//继承hello_grpc.pb.go中的UnimplementedHelloServer结构体
type serverMsg struct {
    pd.UnimplementedHelloServer
}

//实现hello_grpc.pb.go中的接口
func (s *serverMsg) SayHello(contxt context.Context, request *pd.HelloRequest) (*pd.HelloReply, error) {
    return &pd.HelloReply{Message: "hello " + request.Name}, nil
}

func main() {
    //创建服务端Credential:两个参数分别是cerFile(自签证书文件)和keyFile(私钥文件)
    myCredential, err := credentials.NewServerTLSFromFile(
        "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem",
        "D:\\workspace\\goProject\\gRPC-demo\\key\\test.key")
    //开启端口
    listen, _ := net.Listen("tcp", ":8001")
    //创建gRPC服务
    grpcServer := grpc.NewServer(grpc.Creds(myCredential))
    //在gRPC服务端注册我们自己编写的服务
    pd.RegisterHelloServer(grpcServer, &serverMsg{})
    //启动服务
    err = grpcServer.Serve(listen)
    if err != nil {
        fmt.Println("服务端启动失败", err)
        return
    }
}
server.go

客户端

package main

import (
    "context"
    "fmt"
    pd "gRPC-demo/gRPC-client/proto/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func main() {
    //创建客户端Credential:参数分别是cerFile和serverNameOverride
    clientCredential, _ := credentials.NewClientTLSFromFile(
        "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem",
        "*.zhanggen.com")

    conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(clientCredential))
    //客户端请求服务端不携带证书报错
    //conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        fmt.Println("连接错误", err)
    }
    defer conn.Close()
    //建立连接
    client := pd.NewHelloClient(conn)
    //执行RPC调用(这个方法在服务器来实现并返回结果
    response, _ := client.SayHello(context.Background(), &pd.HelloRequest{Name: "zhanggen"})
    fmt.Println(response.GetMessage())
}
client.go

3.Token认证后的服务端和客户端

grpc提供的1个接口,这1个接口有2个方法,接口位于credentials包下,这个接口需要客户端来实现;

  • 第一个方法作用是获取元数据信息,也就是客户端提供的key-value对,context用于控制超时和取消,uri是请求入口处的uri
  • 第二个方法作用是是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS验证,返回值是false则不用
type PerRPCCredentials interface {
    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
    RequireTransportSecurity() bool
}

服务端

package main

import (
    "context"
    "errors"
    "fmt"
    pd "gRPC-demo/gRPC-server/proto/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
    "net"
)

//继承hello_grpc.pb.go中的UnimplementedHelloServer结构体
type serverMsg struct {
    pd.UnimplementedHelloServer
}

//业务代码:实现hello_grpc.pb.go中的接口
func (s *serverMsg) SayHello(ctx context.Context, request *pd.HelloRequest) (*pd.HelloReply, error) {
    //google.golang.org/grpc/metadata,获取客户端请求携带的元数据信息
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, errors.New("未传输token")
    }
    var username string
    var password string
    if v, ok := md["username"]; ok {
        username = v[0]
    }
    if v, ok := md["password"]; ok {
        password = v[0]
    }
    if username != "zhanggen" || password != "123456" {
        return nil, errors.New("token错误")
    }
    return &pd.HelloReply{Message: "hello " + request.Name}, nil
}

func main() {
    //创建服务端Credential:两个参数分别是cerFile(自签证书文件)和keyFile(私钥文件)
    myCredential, err := credentials.NewServerTLSFromFile(
        "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem",
        "D:\\workspace\\goProject\\gRPC-demo\\key\\test.key")
    //开启端口
    listen, _ := net.Listen("tcp", ":8001")
    //创建gRPC服务
    grpcServer := grpc.NewServer(grpc.Creds(myCredential))
    //在gRPC服务端注册我们自己编写的服务
    pd.RegisterHelloServer(grpcServer, &serverMsg{})
    //启动服务
    err = grpcServer.Serve(listen)
    if err != nil {
        fmt.Println("服务端启动失败", err)
        return
    }
}
server.go

客户端

package main

import (
    "context"
    "fmt"
    pd "gRPC-demo/gRPC-client/proto/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

//google.golang.org/grpc/credentials包中Copy出来的代码
//type PerRPCCredentials interface {
//    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
//    RequireTransportSecurity() bool
//}

type ClientTokenAuth struct {
}

func (c *ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{
        "username": "zhanggen",
        "password": "123456",
    }, nil
}
func (c *ClientTokenAuth) RequireTransportSecurity() bool {
    return true
}

func main() {
    //创建客户端Credential:参数分别是cerFile和serverNameOverride
    clientCredential, _ := credentials.NewClientTLSFromFile(
        "D:\\workspace\\goProject\\gRPC-demo\\key\\test.pem",
        "*.zhanggen.com")
    //携带Token
    var opts []grpc.DialOption
    opts = append(opts, grpc.WithTransportCredentials(clientCredential))
    opts = append(opts, grpc.WithPerRPCCredentials(&ClientTokenAuth{}))
    //请求服务端
    conn, err := grpc.Dial("127.0.0.1:8001", opts...)
    //客户端请求服务端不携带证书报错
    //conn, err := grpc.Dial("127.0.0.1:8001", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        fmt.Println("连接错误", err)
    }
    defer conn.Close()
    //建立连接
    client := pd.NewHelloClient(conn)
    //执行RPC调用(这个方法在服务器来实现并返回结果
    response, _ := client.SayHello(context.Background(), &pd.HelloRequest{Name: "zhanggen"})
    fmt.Println(response.GetMessage())
}
client.go

 

参考

posted on 2024-11-03 12:13  Martin8866  阅读(23)  评论(0编辑  收藏  举报