Go语言:使用GRPC实战实践| 青训营

141 阅读7分钟

GRPC实战

安装Protobuf

下载 protobuf: github.com/protocolbuf…

下载并解压到:/tmp/protobuf

安装 gRPC核心库

go get google.golang.org/grpc

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

安装

# 这条语句把 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

proto文件编写

//这是在说明我们使用的是proto3语法。 
syntax ="proto3"; 
// 这部分的内容是关于最后生成的G文件是处在哪个目录哪个包中,,代表在当前目录生成,service代表了生成的GO文件的包名是service。 

option go_package = "./service"; 

// 然后我们需要定义一个服务,在这个服务中需要有一个方法,这个方法可以接受客户端的参数,再返回服务端的响应. // 其实很容易可以看出,我们定义了一个service,称为SayHello,这个服务中有一个rpc方法,名为SayHello。 
// 这个方法会发送一个HelloRequest,然后返回一个HelloResponse。 
service SayHello { 
    rpc SayHello(HelloRequest) returns (HelloResponse) {} 
} 

// message关键字,其实你可以理解为Golang中的结构体。 
// 这里比较特别的是变量后面的“赋值”。注意,这里并不是赋值,而是在定义这个变量在这个message中的位置。 message HelloRequest { 
    string requestName = 1; 
    // int64 age=2; 
} 
message HelloResponse { 
    string responseMsg = 1; 
} 

# 这两条指令都用于生成用于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

客户端代码编写

客户端代码编写

创建与给定目标(服务端) 的连接交互

创建 server的客户端对象

发送 RPC 请求,等待同步响应,得到回调后返回响应结果

输出响应结果

package main 
import ( 
    rp "awesomeProject2/rpc_client/proto" 
    "context" 
    "fmt" 
    "google.golang.org/grpc" 
    "google.golang.org/grpc/credentials/insecure" 
) 
func main() { 
    // 连接到server端,此处禁用安全传输,没有加密和验证 
    conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) 
    if err != nil { 
        fmt.Println(err) 
    } 
    defer conn.Close() 
    // 建立连接 
    client := rp.NewSayHelloClient(conn) 
    // 执行rpc调用(这个方法在服务器端来实现并返回结果 ) 
    data, _ := client.SayHello(context.Background(), &rp.HelloRequest{RequestName: "张三"}) 

    fmt.Println(data.ResponseMsg) }

服务端代码

创建gRPCServer 对象,你可以理解为它是 Server 端的抽象对象

将server(其包含需要被调用的服务端接口)注册到gRPCServer 的内部注册中心

  • 这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理

创建 Listen,监听 TCP 端口

gRPC Server 开始lis.Accept,直到 Stop

package main 
import ( 
    pb "awesomeProject2/rpc_server/proto" 
    "context" 
    "fmt" 
    "google.golang.org/grpc" 
    "net" 
) 

type RServer struct { 
    pb.UnimplementedSayHelloServer 
} 

func (r *RServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { 
    return &pb.HelloResponse{ResponseMsg: "你好" + req.RequestName}, nil 
} 
    
func main() { 
    // 开启端口 
    listen, _ := net.Listen("tcp", ":9090") 
    // 创建grpc服务 
    grpcServer := grpc.NewServer() 
    // 在grpc服务端中去注册我们自己编写的服务 
    pb.RegisterSayHelloServer(grpcServer, &RServer{}) 
    // 启动服务 
    err := grpcServer.Serve(listen) 
    if err != nil { 
        fmt.Println(err) return 
    } 
}

SSL/TSL 认证

安装 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 []: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

服务端代码

创建gRPCServer 对象,你可以理解为它是 Server 端的抽象对象

创建证书认证

将server(其包含需要被调用的服务端接口)注册到gRPCServer 的内部注册中心

  • 这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理

创建 Listen,监听 TCP 端口

gRPC Server 开始lis.Accept,直到 Stop

package main 
import ( 
    pb "awesomeProject2/rpc_server/proto" 
    "context" 
    "fmt" 
    "google.golang.org/grpc" 
    "net" 
) 

type RServer struct { 
    pb.UnimplementedSayHelloServer 
} 

func (r *RServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { 
    return &pb.HelloResponse{ResponseMsg: "你好" + req.RequestName}, nil 
} 
    
func main() { 

    creds, err := credentials.NewServerTLSFromFile( 
        "E:\\GoFworks\\awesomeProject2\\key\\test.pem", 
        "E:\\GoFworks\\awesomeProject2\\key\\test.key", 
    ) 
    if err != nil { 
        fmt.Println(err) return 
    }



    // 开启端口 
    listen, _ := net.Listen("tcp", ":9090") 
    // 创建grpc服务 
    grpcServer := grpc.NewServer(grpc.Creds(creds)) 
    // 在grpc服务端中去注册我们自己编写的服务 
    pb.RegisterSayHelloServer(grpcServer, &RServer{}) 
    // 启动服务 
    err := grpcServer.Serve(listen) 
    if err != nil { 
        fmt.Println(err) return 
    } 
}

客户端代码

创建与给定目标(服务端) 的连接交互

创建证书认证

创建 server的客户端对象

发送 RPC 请求,等待同步响应,得到回调后返回响应结果

输出响应结果

package main 
import ( 
    rp "awesomeProject2/rpc_client/proto" 
    "context" 
    "fmt" 
    "google.golang.org/grpc" 
    "google.golang.org/grpc/credentials/insecure" 
) 
func main() { 

    creds, err := credentials.NewClientTLSFromFile( 
        "E:\\GoFworks\\awesomeProject2\\key\\test.pem", 
        "*.ling.com", 
    )
    // 连接到server端,此处禁用安全传输,没有加密和验证 
    conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(creds)) 
    if err != nil { 
        fmt.Println(err) 
    } 
    defer conn.Close() 
    // 建立连接 
    client := rp.NewSayHelloClient(conn) 
    // 执行rpc调用(这个方法在服务器端来实现并返回结果 ) 
    data, _ := client.SayHello(context.Background(), &rp.HelloRequest{RequestName: "张三"}) 

    fmt.Println(data.ResponseMsg) }

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

服务端代码

创建gRPCServer 对象,你可以理解为它是 Server 端的抽象对象

创建Token的认证

将server(其包含需要被调用的服务端接口)注册到gRPCServer 的内部注册中心

  • 这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理

创建 Listen,监听 TCP 端口

gRPC Server 开始lis.Accept,直到 Stop

package main 

import ( 
    pb "awesomeProject2/rpc_server/proto" 
    "context" 
    "errors" 
    "fmt" 
    "google.golang.org/grpc" 
    "google.golang.org/grpc/credentials/insecure" 
    "google.golang.org/grpc/metadata" 
    "net" 
) 

type RServer struct { 
    pb.UnimplementedSayHelloServer 
} 

func (r *RServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { 
    data, ok := metadata.FromIncomingContext(ctx) 
    if !ok { 
        return nil, errors.New("没有收到数据1") 
    } 
    var appId string 
    var appKey string 
    if v, ok := data["appid"]; ok { 
        appId = v[0] 
    } 
    if v, ok := data["appkey"]; ok { 
        appKey = v[0] 
    } 
    if appId != "ling" || appKey != "123456" { 
        return nil, errors.New("没有收到数据2") 
    } 
    return &pb.HelloResponse{ResponseMsg: "你好" + req.RequestName}, nil 
} 

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

客户端代码

创建与给定目标(服务端) 的连接交互

创建Token的认证

创建 server的客户端对象

发送 RPC 请求,等待同步响应,得到回调后返回响应结果

输出响应结果

package main 
import ( 
    rp "awesomeProject2/rpc_client/proto" 
    "context" "fmt" 
    "google.golang.org/grpc" 
    "google.golang.org/grpc/credentials/insecure" 
) 
type ClientTokenAuth struct { } 
func (auth *ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 
    return map[string]string{ 
        "appId": "ling", 
        "appKey": "123456", 
    }, nil 
} 
func (auth *ClientTokenAuth) RequireTransportSecurity() bool { 
    return false 
} 
func main() { 
    var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) 
    opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth))) 
    // 连接到server端,此处禁用安全传输,没有加密和验证 
    conn, err := grpc.Dial("127.0.0.1:9090", opts...) 
    if err != nil { 
        fmt.Println(err) 
    } 
    defer conn.Close() 
    // 建立连接 
    client := rp.NewSayHelloClient(conn) 
    // 执行rpc调用(这个方法在服务器端来实现并返回结果 ) 
    data, _ := client.SayHello(context.Background(), &rp.HelloRequest{RequestName: "张三"}) 
    fmt.Println(data.ResponseMsg) 
}