微服务
单体架构:
- 某个服务器宕机,整个应用不可用,隔离性差
- 可伸缩性差,浪费资源
- 代码耦合,可维护性差
微服务架构:
-
可以按照服务进行单独扩容
-
各个服务之间可以独立开发,独立部署
新的问题:
-
代码冗余
-
服务和服务之间存在调用关系
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
协议主要解决如下三个网络安全问题
-
保密,通过加密encryption实现,所有信息都加密传输,第三方无法嗅探
-
完整版,通过
MAC
校验机制,一旦被篡改,通信双方会立刻发现 -
认证,双方认证,双方都可以配备证书,放置身份被冒充
生产环境可以购买证书或使用一些平台发放的免费证书
key
:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对客户端接收到数据的解密
csr
:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名
crt
:由证书颁发机机构(CA)签名后的证书,或者是开发者亲自签名的证书,包含持证人的信息,持有人的公钥,以及签署者的签名等信息
pem
:是基于Base64
编码的证书,扩展名包括PEM
、CRT
和CER
TLS
认证实现
首先通过openssl
生成证书和私钥
-
其他人做的便携安装包,Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions
-
配置环境变量
D:\...\OpenSSL-Win64\bin
-
命令行测试
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
-
cd
进key
文件夹中,生成私钥 -
生成证书
-
生成
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)
}