1. GRPC介绍
1.1 什么是RPC
RPC全程是Remote Procedure Call,远程过程调用。这是一种协议,是用来屏蔽分布式计算中的各种调用细节,使得你可以像是本地调用一样直接调用一个远程的函数。
调用流程
1)客户端发送数据(以字节流的方式)编码
2)服务端接受并解析。根据约定知道要执行什么,然后把结果返回给客户 解码
1、RPC就是将上述过程封装起来,使得操作更加优化。
2、使用一些大家都认可的协议,使其规范化
1.2 什么是GRPC
gRPC是谷歌开源的一个高性能的、开源的通用的RPC框架。
我们称调用方为client,被调用方为server。跟其他的RPC框架一样,gRPC也是基于”服务定义“的思想。简单来说就是我们通过某种方式来描述一个服务,这种描述方式是语言无关的。在这个"服务定义"的过程中,我们描述了我们提供的服务服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。
也就是说,在定义好了这些服务、这些方法之后,gRPC会屏蔽底层的细节,clent只需要直接调用定义好的方法,就能拿到预期的返回结果、对于server端来说,还需要实现我们定义的方法。同样的,gRPC也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。
你可以发现,在上面的描述过程中,所谓的”服务定义",就跟定义接口的语义是很接近的。我更愿意理解为这是一种"约定"”,双方约定好接口,然后server实现这个接口,client调用这个接口的代理对象。至于其他的细节,交给gRPC.此外,gRPC还是语言无关的。你可以用C++作为服务端,使用Golang、Java等作为客户端。为了实现这一点,我们在"定义服务“和在编码和解码的过程中,应该是做到语言无关的。
1.3 Protocol Buffers
数据在传输的过程中使用的都是二进制流。gRPC使用了Protocol Buffers,这是谷歌开源的一套成熟的数据结构序列化机制。
protobut是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。
优点:
- 序列化后体积相比json和XML很小,适合网络传输
- 支持跨平台多语言
- 消息格式升级和兼容性还不错
- 序列化和反序列化速度很快
利用这个工具可以把我们定义的方法,转换成特定语言的代码,比如你定义了一种类型的参数,他会帮你转换成Glang中的strudt 结构体,你定义的方法,他会帮你转换成func 函教。此外,在发送请求和接受响应的时候,这个工具还会完成对应的编码和解码工作,将你即将发送的数据编码成RPC能够传输的形式,又或者将即将接收到的数据解码为编程语言能够理解的数据格式。
序列化:将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所产生的二进制串转换成数据结构或者对象的过程
2.protobuf文件编写
hello.proto文件
// 使用的proto的版本
syntax = "proto3";
// 这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中,代表在当前目录生成,service代表了生成的go文件的包名是service
option go_package = ".;service";
// 定义服务
service SayHello{
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
// message关键字,定义结构体变量
message HelloRequest{
string requestName = 1;
int64 age = 2;
// 定义相关的数组
// repeated string follows = 3;
}
message HelloResponse{
string responseMsg = 1;
}
在hello.proto文件夹下运行以下命令生成对应的文件:
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
3. 服务端编写
- 创建gRPC Server对象,你可以理解为它是Server端的抽象对象
- 将server(其包含需要被调用的服务端接口)注册到gRPC Server的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务器端接口并转接进行逻辑处理
- 创建Listen,监听TCP端口
- gRPC Server开始lis.Accept,直到Stop
package main
import (
"context"
"fmt"
hello "go-grpc/hello-server/proto"
"google.golang.org/grpc"
"log"
"net"
"strconv"
)
type HelloService struct {
hello.UnimplementedSayHelloServer
}
func (ds *HelloService) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
fmt.Println("服务器的方法被调用了")
return &hello.HelloResponse{ResponseMsg: in.RequestName + strconv.Itoa(int(in.Age))}, nil
}
func main() {
//绑定9091端口
listener, err := net.Listen("tcp", ":10005")
if err != nil {
log.Fatalf("bingding port:9091 error:%v", err)
}
grpcServer := grpc.NewServer()
var impliServer = &HelloService{}
hello.RegisterSayHelloServer(grpcServer, impliServer)
log.Printf("server listening at %v", listener.Addr())
if err := grpcServer.Serve(listener); err != nil {
panic("error building server: " + err.Error())
}
}
4.客户端编写
- 创建与给定目标(服务端)的连接交互
- 创建server的客户端对象
- 发送RPC请求,等待同步响应,得到回调后返回响应结果
- 输出响应结果
package main
import (
"context"
"flag"
hello "go-grpc/hello-client/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"time"
)
var (
address = flag.String("address", "localhost:10005", "the address connect to ")
)
func main() {
flag.Parse()
connection, err := grpc.Dial(*address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("connect localhost:9091 fail:%v\n", err)
}
defer connection.Close()
client := hello.NewSayHelloClient(connection)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
response, err := client.SayHello(ctx, &hello.HelloRequest{RequestName: "jack", Age: 19})
if err != nil {
log.Fatalf("client call server Sender method fail:%v\n", err)
} else {
log.Println("rpc returns result:", response.ResponseMsg)
}
//获取StudentResponse result的内容
}
5. TLS认证连接
TLS(传输层安全)是一种加密协议,用于在客户端和服务器之间建立安全的通信连接。TLS 在网络通信中起着至关重要的作用,尤其是在保护敏感数据的传输过程中,比如网上银行、电子邮件等。
5.1 修改openssl.cnf
5.2 生成证书
# 1、生成私钥
openssl genrsa -out server.key 2048
# 2、生成证书
openssl req -new -x509 -key server.key -out server.crt -days 36500
# 3、生成csr
openssl req -new -key server.key -out
# 4、生成证书的私钥
openssl genpkey -algorithm RSA -out test.key
# 5、通过私钥test.key生成证书请求文件test.csr
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
# 6、生成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
5.3 修改客户端和服务端代码
server.go
func main() {
//=====================
//TSL认证
//=====================
//两个参数分别是cretFile,keyFile
//自签名证书文件和私钥文件
creds, err := credentials.NewServerTLSFromFile("absolutePath/key/test.pem", "absolutePath/key/test.key")
if err != nil {
return
}
//绑定9091端口
listener, err := net.Listen("tcp", ":10005")
if err != nil {
log.Fatalf("bingding port:9091 error:%v", err)
}
grpcServer := grpc.NewServer(grpc.Creds(creds))
var impliServer = &HelloService{}
hello.RegisterSayHelloServer(grpcServer, impliServer)
log.Printf("server listening at %v", listener.Addr())
if err := grpcServer.Serve(listener); err != nil {
panic("error building server: " + err.Error())
}
}
client.go
func main() {
// 使用证书认证ß
creds, err := credentials.NewClientTLSFromFile("absolutePath/key/test.pem", "*.kuangstudy.com")
flag.Parse()
connection, err := grpc.Dial(*address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("connect localhost:9091 fail:%v\n", err)
}
defer connection.Close()
client := hello.NewSayHelloClient(connection)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
response, err := client.SayHello(ctx, &hello.HelloRequest{RequestName: "jack", Age: 19})
if err != nil {
log.Fatalf("client call server Sender method fail:%v\n", err)
} else {
log.Println("rpc returns result:", response.ResponseMsg)
}
//获取StudentResponse result的内容
}
6. Token认证
实现接口方法:
// PerRPCCredentials 定义了需要为每个 RPC 附加安全信息的凭据的通用接口(例如,oauth2)。
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity 指示凭据是否需要传输安全性。
RequireTransportSecurity() bool
}
6.1 客户端附带信息
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
}
6.2 服务端处理信息
这段代码本来应该写在拦截器里,这里偷懒写在服务里了
func (ds *HelloService) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
// 获取原数据的信息
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errors.New("未传入tokem")
}
var appId string
var appKey string
if v, ok := md["appid"]; ok {
appId = v[0]
}
if v, ok := md["appkey"]; ok {
appId = v[0]
}
//
if appId != "kuangshen" || appKey != "123123" {
return nil, errors.New("Token 不正确")
}
fmt.Println("服务器的方法被调用了")
return &hello.HelloResponse{ResponseMsg: in.RequestName + strconv.Itoa(int(in.Age))}, nil
}