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)
}