gRPC框架

微服务

单体架构:

  • 某个服务器宕机,整个应用不可用,隔离性差
  • 可伸缩性差,浪费资源
  • 代码耦合,可维护性差

微服务架构:

  • 可以按照服务进行单独扩容

  • 各个服务之间可以独立开发,独立部署

新的问题:

  • 代码冗余

  • 服务和服务之间存在调用关系

grpc的优点

  • 生态好:Google

  • 跨语言

  • 性能好

  • 强类型

  • 流式处理

安装gRPC

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

安装protor,etcd,etcdkeeper

protor是可用于通信协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式

// 把解压出来的bin目录放在系统变量中
D:demo_rely\protoc-3.15.5-win64\bin
// cmd中测试
protoc --version
// 下载安装protoc-gen-go
git clone https://gitcode.com/golang/protobuf.git
进入到 protobuf/protoc-gen-go 目录执行以下命令:
go build -o protoc-gen-go main.go
复制 protoc-gen-go.exe 到 GOROOT的 bin 目录
// 我的protoc-gen-go.exe文件在GO/bin目录下
// cmd 转换
cd ./user/
protoc -I internal/service/pb internal/service/pb/*.proto --go_out=plugins=grpc:.
// internal/service/pb pb所在的文件 internal/service/pb/*.proto是需要生成的文件

原生rpc

  • 没有代码提示,易错

  • 编写相对复杂,需要去关注实现过程

// server/ser.go
package main

import (
	"fmt"
	"net"
	"net/http"
	"net/rpc"
)

type Server struct{}

type Req struct {
	Num1 int
	Num2 int
}

type Res struct {
	Num int
}

func (s Server) Add(req Req, res *Res) error {
	res.Num = req.Num1 + req.Num2
	fmt.Println("请求", req)
	return nil
}

func main() {
	// 注册rpc服务
	rpc.Register(new(Server))
	rpc.HandleHTTP()
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println(err)
		return
	}
	http.Serve(listen, nil)
}
// client/cli.go
package main

import (
	"fmt"
	"net/rpc"
)

type Req struct {
	Num1 int
	Num2 int
}

type Res struct {
	Num int
}

func main() {
	req := Req{1, 2}
	client, err := rpc.DialHTTP("tcp", ":8080")
	if err != nil {
		fmt.Println(err)
		return
	}
	var res Res
	err = client.Call("Server.Add", req, &res)
	fmt.Println(res, err)
}

hello word服务器

protobuf文件

// grpc_proto/hello.proto
syntax = "proto3"; // 指定proto版本
package hello_grpc; // 指定默认包名

// go_package表示在当前目录生成go文件,指定生成.go文件时的包名
option go_package = "/hello_grpc";

// 定义rpc服务
service HelloService {
    // 定义函数
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

// HelloRequest 请求内容
message HelloRequest {
    string name = 1;
    string message = 2;
}

// HelloResponse 响应内容
message HelloResponse{
    string name = 1;
    string message = 2;
}

若想让proto文件有高光提示,就在插件中下载gRPC

// 转换
cd grpc_proto
protoc -I . --go_out=plugins=grpc:. .\hello.proto
// 对应的是这个package hello_grpc; // 指定默认报名

服务端

  • 编写一个结构体

  • 实现protobuf中的所有方法

  • 监听端口

  • 注册服务

// server/grpc_server.go
package main

import (
	"context"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"
	"net"

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

type HelloService struct {
}

func (HelloService) SayHello(ctx context.Context, request *hello_grpc.HelloRequest) (res *hello_grpc.HelloResponse, err error) {
	fmt.Println(request)
	return &hello_grpc.HelloResponse{
		Name:    "江小年",
		Message: "ok",
	}, nil
}

func main() {
	// 监听端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		grpclog.Fatalf("Failed to listen: %v", err)
	}
	// 创建一个grpc服务器实例
	s := grpc.NewServer()
	server := HelloService{}
	// 将server结构体注册为grpc服务
	hello_grpc.RegisterHelloServiceServer(s, &server)
	fmt.Println("grpc server running :8080")
	// 开始处理客户端请求
	err = s.Serve(listen)
}

客户端

// grpc_client.go
package main

import (
	"context"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"
	"log"

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

func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
	client := hello_grpc.NewHelloServiceClient(conn)
	result, err := client.SayHello(context.Background(), &hello_grpc.HelloRequest{
		Name:    "江小年",
		Message: "ok",
	})
	fmt.Println(result, err)
}

proto基本数据类型

// grpc_proto/hello.proto
syntax = "proto3"; // 指定proto版本
package hello_grpc; // 指定默认包名

// go_package表示在当前目录生成go文件,指定生成.go文件时的包名
option go_package = "/hello_grpc";

// 定义rpc服务
service HelloService {
    // 定义函数
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

// HelloRequest 请求内容
message HelloRequest {
    string name = 1;
    string message = 2;
}

// HelloResponse 响应内容
message HelloResponse{
    string name = 1;
    string message = 2;
}

proto语法

service对应的就是go里面的接口,可以作为服务端,客户端

rpc对应的就是结构体中的方法

message对应的也是结构体

数据类型

基本数据类型

message Request {
    double a1 = 1;
    float a2 = 2;
    int32 a3 = 3;
    uint32 a4 = 4;
    uint64 a5 = 5;
    sint32 a6 = 6;
    sint64 a7 = 7;
    fixed32 a8 = 8;
    fixed64 a9 = 9;
    sfixed32 a10 = 10;
    sfixed64 a11 = 11;
    bool a12 = 12;
    string a13 = 13;
    bytes a14 = 14;
    repeated string name = 15; // 字符串切片
}

数组类型 repeated关键字

// grpc_study/grpc_proto/type_grpc/type_grpc.proto
syntax = "proto3"; // 指定proto版本
// 指定golang包名
option go_package = "/type_grpc";

service TypeService {
    rpc Say(Request)returns(Response){}
}

message Request{
    double a1 = 1;
    float a2 = 2;
    int32 a3 = 3;
    uint32 a4 = 4;
    uint64 a5 = 5;
    sint32 a6 = 6;
    sint64 a7 = 7;
    fixed32 a8 = 8;
    fixed64 a9 = 9;
    sfixed32 a10 = 10;
    sfixed64 a11 = 11;
    bool a12 = 12;
    string a13 = 13;
    bytes a14 = 14;
}

message Item {
    string name = 1;
    fixed32 code = 2;
}

message ArrayRequest {
    repeated int64 i6_list = 1;
    repeated string s_list = 2;
    repeated Item item_list = 3;
}

message Response{
    
}

map类型

键只能是基本类型

message MapRequest {
	map<int64, string> i_s = 1;
	map<string, bool> s_b = 2;
	map<string, Item> s_item = 3;
}

嵌套类型

message q1 {
	message q2{
		string name = 1;
	}
	string name = 1;
	Q2 q2 = 2;
}

多服务

// grpc_study/grpc_proto/type_grpc/duo.proto
syntax = "proto3"; // 指定proto版本
// 指定golang包名
option go_package = "/duo_proto";

service VideoService {
    rpc Look(Request)returns(Response){}
}

message Request{
	string name = 1;
}

message Response{
	string name = 1;
}

service OrderService {
    rpc Buy(Request)returns(Response){}
}

服务端

// grpc_study/server/duo_server.go
package main

type VideoServer struct {
}

func (VideoServer)Look(ctx context.Context, request *duo_proto.Request) (res *duo_proto.Response, err error) {
    fmt.Println("video:", request)
    return &duo_proto.Response{
        Name: "江小年",
    }, nil
}

type OrderServer struct {
}

func (OrderServer)Buy(ctx context.Context, request *duo_proto.Request) (res *duo_proto.Response, err error) {
    fmt.Println("video:", request)
    return &duo_proto.Response{
        Name: "江小年",
    }, nil
}

func main() {
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    s := grpc.NewServer()
    duo_proto.RegisterVideoServiceServer(s, &VideoServer{})
    duo_proto.RegisterOrderServiceServer(s, &OrderServer{})
    
    fmt.Println("grpc server程序运行在:8080")
    err = s.Serve(listen)
}

客户端

// grpc_study/client/duo_client.go
package main

import (
	"context"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"
	"log"

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

func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
    
	orderClient := duo_proto.NewOrderServiceClient(conn)
    res, err := orderClient.Buy(context.Background(), &duo_proto.Request{
        Name: "江小年",
    })
	fmt.Println(res, err)
    
    videoClient := duo_proto.NewVideoServiceClient(conn)
    res, err = videoClient.Look(context.Background(), &duo_proto.Request{
        Name: "江小年",
    })
	fmt.Println(res, err)
}

proto文件

当项目大起来之后,会有很多个service,rpc,message

我们会将不同的服务放在不同的proto文件中

还可以放一些公共的proto文件

其实本质就是生成go文件,需要在一个包内

头部最好一样,package必须一样

// grpc_study/service_proto/video.proto
syntax = "proto3"; 
package proto;
option go_package = "/proto";
import "common.proto";

service VideoService {
	rpc Look(Request)returns(Response){}
}
// grpc_study/service_proto/order.proto
syntax = "proto3"; 
package proto;
option go_package = "/proto";
import "common.proto";

service OrderService {
	rpc Buy(Request)returns(Response){}
}
// grpc_study/service_proto/common.proto
syntax = "proto3"; 
package proto;
option go_package = "/proto";

message Request{
	string name = 1;
}

message Response{
	string name = 1;
}
// grpc_study/service_proto/XXX.proto
protoc -I .\service_proto --go_out=plugins=grpc:./service_proto .\service_proto\video.proto
protoc -I .\service_proto --go_out=plugins=grpc:./service_proto .\service_proto\order.proto
protoc -I .\service_proto --go_out=plugins=grpc:./service_proto .\service_proto\common.proto

服务端流式传输

// grpc_study/stream_proto/stream.proto
syntax = "proto3";
option go_package = "/proto";

service Simple{
	rpc Fun(Request)returns(Response){}
}

message Request {
	string name = 1;
}

message Response {
	string Text = 1;
}
// 服务端,客户端代码同上
// 服务端流式
service ServiceStream{
	rpc Fun(Request)returns(stream Response){}
}
// grpc_study/server/服务端流式serve.go
package main

import "grpc_study/stream_proto/proto"

type ServiceStream struct {}

func (ServiceStream)Fun(request *proto.Request, stream proto.ServiceStream_FunServer) error {
    fmt.Println(request)
    for i := 0; i < 10; i++ {
        stream.Send(&proto.Response{
            Text: fmt.Sprintf("第%d轮数据", i)
        }) // 响应
    }
    return nil
}

func main() {
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    server := grpc.NewServer()
    proto.RegisterServiceStreamServer(server, &ServiceStream)
    
    server.Server(listen)
}
// grpc_study/client/服务端流式client.go
func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
	client := proto.NewServiceStreamClient(conn)
	stream, err := client.Fun(context.Background(), &proto.Request{
		Name:    "江小年",
	})
    for i := 0; i < 10; i++ {
        response, err := stream.Recv()
        fmt.Println(response, err)
    }
}

客户端不知道服务端什么时候结束

服务端流式案例_下载文件

客户端不知道服务端什么时候结束

// grpc_study/client/服务端流式client.go
func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
	client := proto.NewServiceStreamClient(conn)
	stream, err := client.Fun(context.Background(), &proto.Request{
		Name:    "江小年",
	})
    for {
        response, err := stream.Recv()
        if err == io.EOF {
            // EOF就是结束的err报错
            break
        }
        fmt.Println(response)
    }
}

下载文件,文件大时需要多次传输,这时不知道多会儿结束怎么办

// grpc_study/stream_proto/stream.proto
syntax = "proto3";
option go_package = "/proto";

service Simple{
	rpc Fun(Request)returns(Response){}
}

message Request {
	string name = 1;
}

message Response {
	string Text = 1;
}

message FileResponse {
	string file_name = 1;
    bytes content = 2;
}

// 服务端,客户端代码同上
// 服务端流式
service ServiceStream{
	rpc Fun(Request)returns(stream Response){}
    rpc DownLoadFile(Request)returns(stream FileResponse){}
}
// protoc -I . --go_out=plugins=grpc:./stream_proto .\stream_proto\stream.proto

创建一个文件夹static放一个大文件

// grpc_study/server/服务端流式serve.go
package main

import "grpc_study/stream_proto/proto"

type ServiceStream struct {}

func (ServiceStream)Fun(request *proto.Request, stream proto.ServiceStream_FunServer) error {
    fmt.Println(request)
    for i := 0; i < 10; i++ {
        stream.Send(&proto.Response{
            Text: fmt.Sprintf("第%d轮数据", i)
        }) // 响应
    }
    return nil
}

func (ServiceStream)DownLoadFile(request *proto.Request, stream proto.ServiceStream_DownLoadFileServer) error {
    fmt.Println(request)
    file, err := os.Open("abc.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    for {
        buf := make([]byte, 2048)
        _, err = file.Read(buf)
        if err == io.EOF {
            break
        }

        if err != nil {
            break
        }
        stream.Send(&proto.FileResponse{
            Content: buf,
        })
    }
    return nil
}

func main() {
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    server := grpc.NewServer()
    proto.RegisterServiceStreamServer(server, &ServiceStream)
    
    server.Server(listen)
}
// grpc_study/client/服务端流式client.go
func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
	client := proto.NewServiceStreamClient(conn)
	stream, err := client.DownLoadFile(context.Background(), &proto.Request{
		Name:    "江小年",
	})

    file, err := os.OpenFile("abc.txt", os.O_CREATE|os.O_WRONLY, 0600)
    if err != nil {
        log.Fatalln(err)
    }
    defer file.Close() 

    writer := bufio.NewWriter(file)
    var index int
    for {
        index ++
        response, err := stream.Recv()
        if err == io.EOF {
            // EOF就是结束的err报错
            break
        }
        fmt.Printf("第%d次 写入 %d 数据", index, len(response.Content))
        writer.Writer(response.Content)
    }
    writer.Flush()
}

客户端流式传输

// grpc_study/stream_proto/stream.proto
syntax = "proto3";
option go_package = "/proto";

service Simple{
	rpc Fun(Request)returns(Response){}
}

message Request {
	string name = 1;
}

message Response {
	string Text = 1;
}

message FileResponse {
	string file_name = 1;
    bytes content = 2;
}

// 服务端,客户端代码同上
// 服务端流式
service ServiceStream{
	rpc Fun(Request)returns(stream Response){}
    rpc DownLoadFile(Request)returns(stream FileResponse){}
}

message FileRequest {
	string file_name = 1;
    bytes content = 2;
}

// 客户端流式
service ClientStream {
	rpc UploadFile(stream FileRequest)returns(Response){}
}

// protoc -I . --go_out=plugins=grpc:./stream_proto .\stream_proto\stream.proto
// grpc_study/server/客户端流式serve.go
package main

import "grpc_study/stream_proto/proto"

type ClientStream struct {}


func (ClientStream)UpLoadFile(stream proto.ClientStream_UploadFileServer) error {
    for i := 0; i < 10; i++ {
        response, err := stream.Recv()
        fmt.Println(response, err)
    }
    stream.SendAndClose(&proto.Response{Text: "完毕"})
    return nil
}

func main() {
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    server := grpc.NewServer()
    proto.RegisterClientStreamServer(server, &ClientStream)
    
    server.Server(listen)
}
// grpc_study/client/客户端流式client.go
func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
    client := proto.NewClientStreamClient(conn)
    stream, err := client.UploadFile(context.Background())
    for i := 0; i < 10; i++ {
        stream.Send(&proto.FileRequest{FileName: fmt.Sprintf("第%d次", i)})
    }
    response, err := stream.CloseAndRecv()
    fmt.Println(response, err)
}

客户端流式案例_上传文件

// grpc_study/client/客户端流式client.go
func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
    client := proto.NewClientStreamClient(conn)
    stream, err := client.UploadFile(context.Background())
    
    file, err := os.Open("abc.txt")
    if err != nil {
        log.Fatalln(err)
    }
    defer file.Close()

    for {
        buf := make([]byte, 2048)
        _, err = file.Read(buf)
        if err == io.EOF {
            break
        }

        if err != nil {
            break
        }
        stream.Send(&proto.FileRequest{
            FileName: "x.png"
            Content: buf,
        })
    }

    response, err := stream.CloseAndRecv()
    fmt.Println(response, err)
}
// grpc_study/server/客户端流式serve.go
package main

import "grpc_study/stream_proto/proto"

type ClientStream struct {}


func (ClientStream)UpLoadFile(stream proto.ClientStream_UploadFileServer) error {
    file, err := os.OpenFile("abc.txt", os.O_CREATE|os.O_WRONLY, 0600)
    if err != nil {
        log.Fatalln(err)
    }
    defer file.Close() 

    writer := bufio.NewWriter(file)
    var index int
    for{
        index ++
        response, err := stream.Recv()
        if err == io.EOF {
            break
        }
        writer.Writer(response.Context)
        fmt.Printf("第%d次", index)
    }
    writer.Flush()
    stream.SendAndClose(&proto.Response{Text: "完毕"})
    return nil
}

func main() {
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    server := grpc.NewServer()
    proto.RegisterClientStreamServer(server, &ClientStream)
    
    server.Server(listen)
}

双向流传输

// grpc_study/stream_proto/stream.proto
syntax = "proto3";
option go_package = "/proto";

service Simple{
	rpc Fun(Request)returns(Response){}
}

message Request {
	string name = 1;
}

message Response {
	string Text = 1;
}

message FileResponse {
	string file_name = 1;
    bytes content = 2;
}

// 服务端,客户端代码同上
// 服务端流式
service ServiceStream{
	rpc Fun(Request)returns(stream Response){}
    rpc DownLoadFile(Request)returns(stream FileResponse){}
}

message FileRequest {
	string file_name = 1;
    bytes content = 2;
}

// 客户端流式
service ClientStream {
	rpc UploadFile(stream FileRequest)returns(Response){}
}

// 双向流传输
service BothStream{
	rpc Chat(stream Request)returns(stream Response){}
}

// protoc -I . --go_out=plugins=grpc:./stream_proto .\stream_proto\stream.proto
// grpc_study/server/双向流式serve.go
package main
type BothStream struct {}


func (BothStream)Chat(stream proto.BothStream_ChatServer) error {
    for i := 0; i < 10; i++ {
        request, _ := stream.Recv()
        fmt.Println(request)
        stream.Send(&proto.Response{
            Text: "你好",
        })
    }
    return nil
}

func main() {
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    server := grpc.NewServer()
    proto.RegisterBothStreamServer(server, &BothStream)
    
    server.Server(listen)
}
// grpc_study/client/双向流式client.go
func main() {
	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
    client := proto.NewBothStreamClient(conn)
    stream, err := client.Chat(context.Background())
    
    for i:=0;i<10;i++{
        stream.Send(&proto.Request{
            Name: fmt.Sprintf("第%d次", i)
        })
        response, err := stream.Recv()
        fmt.Println(response, err)
    }
}

认证及安全传输说明

认证不是用户的身份认证,而是指多个server和client之间,如何识别对方是谁,并且可以安全的进行数据传输

  • SSL/TLS认证方式(采用http2协议)

  • 基于Token的认证方式(基于安全连接)

TLS协议主要解决如下三个网络安全问题

  1. 保密,通过加密encryption实现,所有信息都加密传输,第三方无法嗅探

  2. 完整版,通过MAC校验机制,一旦被篡改,通信双方会立刻发现

  3. 认证,双方认证,双方都可以配备证书,放置身份被冒充

生产环境可以购买证书或使用一些平台发放的免费证书

key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对客户端接收到数据的解密

csr:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名

crt:由证书颁发机机构(CA)签名后的证书,或者是开发者亲自签名的证书,包含持证人的信息,持有人的公钥,以及签署者的签名等信息

pem:是基于Base64编码的证书,扩展名包括PEMCRTCER

TLS认证实现

首先通过openssl生成证书和私钥

生成证书

# 1、生成私钥
openssl genrsa -out server.key 2048
# 2、生成证书 全部回车即可,可以不填
openssl req -new -x509 -key server.key -out server.crt -days 36500
# 国家名称
Country Name (2 letter code) [Au]:CN
# 省名称
State or Province Name (full name) [Some-State]:GuangDong
# 城市名称
Locality Name (eg, city) []:Meizhou
# 公司组织名称
Organization Name (eg, company) [Internet widgits Pty Ltd]:Xuexiangban
# 部门名称
Organizational Unit Name (eg, section) []:go
# 服务器or网站名称
Common Name (e.g. server FQDN or YOUR name) []:kuangstudy
# 邮件
Email Address []:3509309412@qq.com
​
# 3、生成csr
openssl req -new -key server.key -out server.csr
# 更改openssl.cnf (Linux是openssl.cfg)
# 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 ],和标签字段 DNS.1 = *.huajiangsll.com
# 步骤5)是设置能访问的网站
# 生成证书私钥test.key
openssl genpkey -algorithm RSA -out test.key
​
# 通过私钥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
# test.csr是上面生成的证书请求文件。 ca.crt/server.key是CA证书文件和key,用来对test.csr进行签名认证。这两个文件在第一部分生成
​
# 生成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
  • 新建一个文件夹key

  • cdkey文件夹中,生成私钥

  • 生成证书

  • 生成csr

  • 复制一份openssl.cnf文件到你项目所在的证书目录(key)下

// server/grpc_server.go
package main

import (
	"context"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"
	"net"

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

type HelloService struct {
}

func (HelloService) SayHello(ctx context.Context, request *hello_grpc.HelloRequest) (res *hello_grpc.HelloResponse, err error) {
	fmt.Println(request)
	return &hello_grpc.HelloResponse{
		Name:    "江小年",
		Message: "ok",
	}, nil
}

func main() {
	creds, _ := credentials.NewServerTLSFromFile("D:\\Godemo\\end_demo\\练习\\grpc_test\\key\\test.pem",
		"D:\\Godemo\\end_demo\\练习\\grpc_test\\key\\test.key")
	// 监听端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		grpclog.Fatalf("Failed to listen: %v", err)
	}
	// 创建一个grpc服务器实例
	s := grpc.NewServer(grpc.Creds(creds))
	server := HelloService{}
	// 将server结构体注册为grpc服务
	hello_grpc.RegisterHelloServiceServer(s, &server)
	fmt.Println("grpc server running :8080")
	// 开始处理客户端请求
	err = s.Serve(listen)
}
// grpc_client.go
package main

import (
	"context"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"
	"log"

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

func main() {
	creds, _ := credentials.NewClientTLSFromFile("D:\\Godemo\\end_demo\\练习\\grpc_test\\key\\test.pem",
		"*.helloword.com")

	addr := ":8080"
	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	}
	defer conn.Close()
	// 初始化客户端
	client := hello_grpc.NewHelloServiceClient(conn)
	result, err := client.SayHello(context.Background(), &hello_grpc.HelloRequest{
		Name:    "江小年",
		Message: "ok",
	})
	fmt.Println(result, err)
}

Token认证

gRPC提供的一个接口,接口中有两个方法,接口位于credentials包下,这个接口需要客户端来实现

type PerRPCCredentials interface {
    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
    RequireTransportSecurity() bool
}

第一个方法就是获取元数据信息,也就是客户端提供的key.value对,context用于控制超时和取消,uri是请求入口处的uri

第二个方法的作用是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS认证,返回值是false则不用

// grpc_client.go
package main

import (
	"context"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"

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

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{
		"appId":  "jiangxiaonian",
		"appKey": "123456",
	}, nil
}

func (c ClientTokenAuth) RequireTransportSecurity() bool {
	return false
}

func main() {

	addr := ":8080"

	// creds, _ := credentials.NewClientTLSFromFile("D:\\Godemo\\end_demo\\练习\\grpc_test\\key\\test.pem",
	// 	"*.helloword.com")

	var opts []grpc.DialOption
	opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
	opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))
	conn, err := grpc.Dial(addr, opts...)

	// 使用 grpc.Dial 创建一个到指定地址的 gRPC 链接
	// 此处使用不安全的证书来实现 SSL/TLS
	// conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
	// if err != nil {
	// 	log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))
	// }
	defer conn.Close()
	// 初始化客户端
	client := hello_grpc.NewHelloServiceClient(conn)
	result, err := client.SayHello(context.Background(), &hello_grpc.HelloRequest{
		Name:    "江小年",
		Message: "ok",
	})
	fmt.Println(result, err)
}

<!-- 服务端应该写一个拦截器 -->

// server/grpc_server.go
package main

import (
	"context"
	"errors"
	"fmt"
	"grpc_test/grpc_proto/hello_grpc"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/metadata"
)

type HelloService struct {
}

func (HelloService) SayHello(ctx context.Context, request *hello_grpc.HelloRequest) (res *hello_grpc.HelloResponse, err error) {

	// 获取元数据的信息
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, errors.New("未传输token")
	}
	var appId string
	var appKey string
	if v, ok := md["appid"]; ok {
		appId = v[0]
	}
	if v, ok := md["appkey"]; ok {
		appKey = v[0]
	}
	// 用户id 查appid
	if appId != "jiangxiaonian" || appKey != "123456" {
		return nil, errors.New("token不正确")
	}

	fmt.Println(request)
	return &hello_grpc.HelloResponse{
		Name:    "江小年",
		Message: "ok",
	}, nil
}

func main() {
	// creds, _ := credentials.NewServerTLSFromFile("D:\\Godemo\\end_demo\\练习\\grpc_test\\key\\test.pem",
	// 	"D:\\Godemo\\end_demo\\练习\\grpc_test\\key\\test.key")
	// 监听端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		grpclog.Fatalf("Failed to listen: %v", err)
	}
	// 创建一个grpc服务器实例
	s := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
	server := HelloService{}
	// 将server结构体注册为grpc服务
	hello_grpc.RegisterHelloServiceServer(s, &server)
	fmt.Println("grpc server running :8080")
	// 开始处理客户端请求
	err = s.Serve(listen)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江小年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值