grpc 狂神笔记

基本结构

目录结构

.
├── go.mod
├── go.sum
├── hello-client
│   ├── main.go
│   └── proto
│       └── hello.proto
└── hello-server
    ├── main.go
    └── proto
        ├── gen.sh
        ├── hello.pb.go
        ├── hello.proto
        └── hello_grpc.pb.go

工具准备

安装 Protobuf

  1. 下载 protobuf: https://github.com/protocolbuffers/protobuf/releases
    下载并解压到:/tmp/protobuf
  2. 配置环境变量
vim /etc/profile
export PATH=$PATH:/tmp/protobuf/bin
# 或者
cp /tmp/protobuf/bin/protoc /usr/local/bin
  1. 安装 gRPC核心库
go get google.golang.org/grpc

protocol 编译器,可以生成不同语言的代码

  1. 安装
# 这条语句把 go install 安装的包的所在可执行目录加入到环境变量
# 不然使用 go install 后找不到安装的包
export PATH=$PATH:/root/go/bin

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

客户端代码编写

main.go

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	pb "xxx-grpc-demo/hello-server/proto"

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

func main() {
	start := time.Now() // 记录开始时间
	
	cc, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer cc.Close()

	// 建立连接
	client := pb.NewSayHelloClient(cc)
	
	// 执行 rpc 调用(这个方法在服务器端来实现并返回结果)
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kuangshen"})

	fmt.Println(resp.GetResponseMsg())

	elapsed := time.Since(start) // 计算经过的时间
	fmt.Println("程序运行耗时:", elapsed)
}

服务端代码

main.go

package main

import (
	"context"
	"fmt"
	"net"
	pb "xxx-grpc-demo/hello-server/proto"

	"google.golang.org/grpc"
)

type server struct {
	pb.UnimplementedSayHelloServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
	fmt.Printf("hello" + req.RequestName)
	return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}

func main() {
	// 开启端口
	listen, _ := net.Listen("tcp", ":9090")
	// 创建 grpc 服务
	grpcServer := grpc.NewServer()
	// 在 grpc 服务中注册我们自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})
	// 启动服务
	err := grpcServer.Serve(listen)
	if err != nil {
		fmt.Printf("failed to serve: %v", err)
		return
	}
}

hello.proto 文件

// 使用proto3语法
syntax = "proto3";

// 生成的go文件在哪个目录哪个包中, . 表示在当前目录生成,service 代表生成的go文件的包名是service
option go_package = ".;service";

// 定义服务,其中有个方法,接收客户端参数,返回服务端响应
service SayHello {
    rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

// 对应golang里面的结构体
// 这里面的 “赋值” 代表这个变量在这个 message 中的位置
message HelloRequest {
    string requestName = 1;
}

message HelloResponse {
    string responseMsg = 1;
}

gen.sh 编写

代码生成脚本

#!/bin/bash

# 这两条指令都用于生成用于Go语言的Protocol Buffers代码。

# - `protoc --go_out=. hello.proto`:这条指令使用`--go_out`插件,
# 它会生成与`hello.proto`文件中定义的消息类型和服务相关的Go代码。
# 生成的代码包含用于序列化和反序列化消息的方法,以及用于访问和操作消息字段的方法。

# - `protoc --go-grpc_out=. hello.proto`:这条指令使用`--go-grpc_out`插件,
# 它会生成与`hello.proto`文件中定义的gRPC服务相关的Go代码。
# 生成的代码包含用于实现gRPC服务接口的方法,以及用于创建gRPC客户端和服务器的辅助函数。

# 总结来说,`--go_out`生成的代码用于处理消息的序列化和反序列化,
# 而`--go-grpc_out`生成的代码用于实现和使用gRPC服务。
# 如果您只需要处理消息的序列化和反序列化,那么只需使用`--go_out`即可。
# 如果您还要使用gRPC服务,那么需要同时使用`--go_out`和`--go-grpc_out`。

protoc --go_out=. hello.proto

protoc --go-grpc_out=. hello.proto

SSL/TSL 认证

  1. 安装 openssl
    博客: https://blog.csdn.net/u012670181/article/details/104102110?spm=1001.2014.3001.5506
sudo apt update
sudo apt install build-essential checkinstall zlib1g-dev -y

cd /usr/local/src/
sudo wget https://www.openssl.org/source/openssl-1.1.1b.tar.gz

sudo tar -xf openssl-1.1.1b.tar.gz 
cd openssl-1.1.1b

sudo ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib
 
sudo make
sudo make test

注意:

  • prefix和–openssldir =设置OpenSSL的输出路径。
  • shared = force来创建共享库。
  • zlib =使用zlib库启用压缩。
make install

echo "/usr/local/ssl/lib" >> /etc/ld.so.conf.d/openss1-1.1.1b.conf

ldconfig -v

sudo mv /usr/bin/c_rehash /usr/bin/c_rehash.BEKUP
sudo mv /usr/bin/openssl /usr/bin/openssl.BEKUP

vim /etc/environment
# 加入
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/ssl/bin"

source /etc/environment
echo $PATH

openssl version -a

apt-get install libssl-dev
  1. 生成证书
# 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 []:2473674@qq.com

# 3、生成 csr
openssl req -new -key server.key -out server.csr
# 更改 openssl.cnf (linux 是 openssl.cfg)
# 1) 复制一份你安装的 openssl 的 bin 目录里面的 openssl.cnf 文件到你项目所在的目录 
# linux: /usr/local/ssl/openssl.cnf
# cp /usr/local/ssl/openssl.cnf dst
# 2) 找到 [ CA_default ] 打开 copy_extensions = copy (去掉前面的 # )
# 3) 找到 [ req ] 打开 req_extensions = v3_req (去掉前面的 # )
# 4) 找到 [ v3_req ] 添加 subjectAltName = @alt_names
# 5) 添加新的标签 [ alt_names ],和标签字段
DNS.1 = *.kuangstudy.com
# 生成证书私钥
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.cnf \
-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.cnf \
-extensions v3_req 

目录结构

.
├── go.mod
├── go.sum
├── hello-client
│   ├── main.go
│   └── proto
│       └── hello.proto
├── hello-server
│   ├── main.go
│   └── proto
│       ├── gen.sh
│       ├── hello.pb.go
│       ├── hello.proto
│       └── hello_grpc.pb.go
└── key
    ├── openssl.cnf
    ├── server.crt
    ├── server.csr
    ├── server.key
    ├── server.srl
    ├── test.csr
    ├── test.key  # 私钥
    └── test.pem  # 证书

服务端代码

package main

import (
	"context"
	"fmt"
	"net"
	pb "xxx-grpc-demo/hello-server/proto"

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

type server struct {
	pb.UnimplementedSayHelloServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
	fmt.Printf("hello" + req.RequestName)
	return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}

func main() {
	creds, _ := credentials.NewServerTLSFromFile("/root/Project/grpc-demo/key/test.pem", "/root/Project/grpc-demo/key/test.key")
	// 开启端口
	listen, _ := net.Listen("tcp", ":9090")
	// 创建 grpc 服务
	grpcServer := grpc.NewServer(grpc.Creds(creds))
	// 在 grpc 服务中注册我们自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})
	// 启动服务
	err := grpcServer.Serve(listen)
	if err != nil {
		fmt.Printf("failed to serve: %v", err)
		return
	}
}

客户端代码

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	pb "xxx-grpc-demo/hello-server/proto"

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

func main() {
	creds, _ := credentials.NewClientTLSFromFile("/root/Project/grpc-demo/key/test.pem", "*.kuangstudy.com")
	start := time.Now() // 记录开始时间

	cc, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer cc.Close()

	// 建立连接
	client := pb.NewSayHelloClient(cc)
	
	// 执行 rpc 调用(这个方法在服务器端来实现并返回结果)
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kuangshen"})

	fmt.Println(resp.GetResponseMsg())

	elapsed := time.Since(start) // 计算经过的时间
	fmt.Println("程序运行耗时:", elapsed)
}

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则不用

服务端代码

package main

import (
	"context"
	"errors"
	"fmt"
	"net"
	pb "xxx-grpc-demo/hello-server/proto"

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

type server struct {
	pb.UnimplementedSayHelloServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, 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]
		fmt.Println("appID:", appId)
	}
	if v, ok := md["appkey"]; ok {
		appKey = v[0]
		fmt.Println("appKey:", appKey)
	}
	if appId != "kuangshen" || appKey != "123123" {
		return nil, errors.New("token 不正确")
	}

	fmt.Printf("hello" + req.RequestName)
	return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}

func main() {
	// 开启端口
	listen, _ := net.Listen("tcp", ":9090")
	// 创建 grpc 服务
	grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
	// 在 grpc 服务中注册我们自己编写的服务
	pb.RegisterSayHelloServer(grpcServer, &server{})
	// 启动服务
	err := grpcServer.Serve(listen)
	if err != nil {
		fmt.Printf("failed to serve: %v", err)
		return
	}
}

客户端代码

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	pb "xxx-grpc-demo/hello-server/proto"

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

type ClientTokenAuth struct {
}

func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appId":  "kuangshen",
		"appKey": "123123",
	}, nil
}

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

func main() {
	start := time.Now() // 记录开始时间

	var opts []grpc.DialOption
	opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
	opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))

	cc, err := grpc.Dial("127.0.0.1:9090", opts...)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer cc.Close()

	// 建立连接
	client := pb.NewSayHelloClient(cc)

	// 执行 rpc 调用(这个方法在服务器端来实现并返回结果)
	resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kuangshen"})

	fmt.Println(resp.GetResponseMsg())

	elapsed := time.Since(start) // 计算经过的时间
	fmt.Println("程序运行耗时:", elapsed)
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值