gRPC
1 gRPC
gRPC是什么?
根据官方文档的话来说,A high performance, open source universal RPC framework,也就是一个高性能的开源RPC框架。
在gRPC中,我们称调用方为client,被调用方为server。跟其他RPC框架一样,gRPC也是基于 服务定义 思想。简单来说,就是我们通过一种方式来描述一个服务,这种描述方式跟语言无关。在这个服务定义的过程中,我们描述了我们提供的服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。
也就是说,在定义好这些服务,这些方法之后,gRPC会屏蔽底层的细节,client只需要直接调用定义好的方法,就能拿到预期的返回结果。对于server端来说,还需要实现我们定义的方法。同样的,gRPC也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。
在上述描述过程中,所谓的"服务定义",跟接口是很接近的。更像是一种约定,双方约定好接口,然后server实现这个接口,client调用这个接口的代理对象。至于其他的细节,交给gRPC。
此外,gRPC还是语言无关的。可以使用C++作为服务端,使用Java,Go等作为客户端。为了实现这一点,在定义服务和编码和解码的过程中,应该做到语言无关。
因此,gRPC使用了 Protocol Buffss
作为接口描述语言,这是谷歌开源的一套成熟的数据结构序列化机制。
这个工具可以把我们定义的方法,转换成特定语言的代码。比如你定义了一种类型的参数,他会帮你转换成Go中的struct结构体,你定义的方法,他会帮你转换成func函数,此外,在发送请求和接受响应的时候,这个工具还会完成对应的编码和解码工作,将你即将发送的数据编码成gRPC能够传输的形式,又或者将即将接收到的数据解码为编程语言能够理解的数据格式。
序列化:将数据结构或对象转换成二进制串的过程 反序列化:将在序列化过程中所产生的二进制串转换成数据结构或对象的过程
protobuf
是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为protobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。
优势:
序列化后的体积比XML/JSON更加,适合网络传输
支持跨平台多语言
消息格式升级和兼容性不错
序列化和反序列化速度很快
可以查看gPRC中文文档,或gPRC官方文档查看更多gRPC详细信息。
2 gRPC使用
下载
go get google.golang.org/grpc // gRPC库
// 按照生成go代码的插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
使用gRPC之前要先编写Protobuf。
3 编写服务端
创建 gRPC Server对象,可以理解为是Server端的抽象对象
将server(包含需要被调用的服务端接口)注册到gRPC Server的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
创建Listen,监听TCP端口
gRPC Server开始list.Accept,直到Stop
package main
import (
"context"
"fmt" pb "go-rpc/hello-server/proto"
"google.golang.org/grpc" "net")
type server struct {
pb.UnimplementedSayHelloServer
}
func (s *server) SayHello(c context.Context, re *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{ResponseMsg: fmt.Sprintf("%s SayHello", re.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.Println(err)
}
}
4 编写客户端
创建与给定目标(服务端)的连接交互
创建Server的客户端对象
发送RPC请求,等待同步响应,得到回调后返回响应结果
输出响应结果
package main
import (
"context"
"fmt" pb "go-rpc/hello-server/proto"
"google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "log")
func main() {
// 连接到server端,此处禁用安全传输,没有加密和验证
c, err := grpc.NewClient("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 建立连接
client := pb.NewSayHelloClient(c)
// 执行rpc调用,在服务器端实现并返回结果
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kelly"})
fmt.Println(resp.GetResponseMsg())
}
5 认证-安全传输
gRPC
是一个典型的C/S模型,需要开发客户端和服务端,客户端与服务端需要达成协议,使用某一个确认的传输协议来传输数据,gRPC通常默认是使用protobuf来作为传输协议,当然也可以使用其他自定义的。
所以需要使用一种认证,来让客户端知道自己的数据是发送给哪一个明确的服务端,让服务端知道自己的数据要返回给谁。
此处说到的认证,不是用户的身份认证,而是指多个server和多个client之间,如何识别对方是谁,并且可以安全的进行数据传输。
SSL/TLS认证方式(采用http2协议)
基于token的认证方式(基于安全连接)
不采用任何措施的连接,这是不安全的连接(默认采用http1)
自定义的身份认证
客户端和服务端之间调用,可以通过加入证书的方式,实现调用的安全性。
TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCp进行传输的功能
TLS主要解决如下三个网络安全问题:
保密,保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探
完整性,通过MAC校验机制,一旦被篡改,通信双方会立刻发现
认证,双方认证,双方都可以配备证书,防止身份被冒充
生产环境可以购买证书或者使用一些平台发放的免费证书
key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密
csr:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名
crt:由证书颁发机构签名后的证书,或者是开发者自签名的证书,包含证书持有人信息,持有人公钥,以及签署者的签名信息
pem:是基于Base64编码的证书格式,扩展名包括PEM,CRT和CER
5.1 SSL/TLS认证方式
// 生成私钥
openssl genrsa -out server.key 2048
// 生成证书
openssl req -new -x509 -key server.key -out server.crt -days 36500
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
// 生成csr
openssl req -new -key server.key -out server.csr
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# 复制一份openssl.cnf文件到当前项目,修改以下内容
# Extension copying option: use with caution.
copy_extensions = copy
req_extensions = v3_req # The extensions to add to a certificate request
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = *.kelly.com
%% 1. **`copy_extensions = copy`**
- **作用**:允许在签发证书时,将证书请求(CSR)中的扩展字段(如SAN)复制到最终证书中。
- **场景**:当CA需要继承CSR中的扩展信息时使用,确保动态生成的SAN等信息被包含在证书里。
2. **`req_extensions = v3_req`**
- **作用**:指定生成证书请求时应包含的扩展部分,此处指向`[v3_req]`块。
- **场景**:在创建CSR时激活X.509 v3扩展,如主题备用名称(SAN)。
3. **[v3_req] 块中的 `subjectAltName = @alt_names`**
- **作用**:定义主题备用名称(SAN),引用`alt_names`部分的列表。
- **场景**:为证书配置多个域名或IP地址 %%
// 生成证书私钥
openssl genpkey -algorithm RSA -out test.key
// 通过私钥生成证书请求文件
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
// 生成SAN证书
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
// server
package main
import (
"context"
"fmt" pb "go-rpc/hello-server/proto"
"google.golang.org/grpc" "google.golang.org/grpc/credentials" "net")
type server struct {
pb.UnimplementedSayHelloServer
}
func (s *server) SayHello(c context.Context, re *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{ResponseMsg: fmt.Sprintf("%s SayHello", re.RequestName)}, nil
}
func main() {
// 加载证书和私钥
creds, err := credentials.NewServerTLSFromFile("/Users/ayohuang/GolandProjects/go-rpc/key/test.pem", "/Users/ayohuang/GolandProjects/go-rpc/key/test.key")
if err != nil {
fmt.Println(err)
}
// 开启端口
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.Println(err)
}
}
// client
package main
import (
"context"
"fmt" pb "go-rpc/hello-server/proto"
"google.golang.org/grpc" "google.golang.org/grpc/credentials" "log")
func main() {
creds, err := credentials.NewClientTLSFromFile("/Users/ayohuang/GolandProjects/go-rpc/key/test.pem", "*.kelly.com")
c, err := grpc.NewClient("localhost:9090", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 建立连接
client := pb.NewSayHelloClient(c)
// 执行rpc调用,在服务器端实现并返回结果
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kelly"})
fmt.Println(resp.GetResponseMsg())
}
5.2 Token认证
// server
package main
import (
"context"
"errors" "fmt" pb "go-rpc/hello-server/proto"
"google.golang.org/grpc" "google.golang.org/grpc/metadata" "net")
type server struct {
pb.UnimplementedSayHelloServer
}
func (s *server) SayHello(c context.Context, re *pb.HelloRequest) (*pb.HelloResponse, error) {
md, ok := metadata.FromIncomingContext(c)
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]
}
if appId != "kelly" || appKey != "123132" {
return nil, errors.New("token信息错误")
}
return &pb.HelloResponse{ResponseMsg: fmt.Sprintf("%s SayHello", re.RequestName)}, nil
}
func main() {
//creds, err := credentials.NewServerTLSFromFile("/Users/ayohuang/GolandProjects/go-rpc/key/test.pem", "/Users/ayohuang/GolandProjects/go-rpc/key/test.key")
//if err != nil { // fmt.Println(err) //}
// 开启端口
listen, _ := net.Listen("tcp", ":9090")
// 创建gRPC服务
grpcServer := grpc.NewServer()
// 在grpc服务端注册自己编写的服务
pb.RegisterSayHelloServer(grpcServer, &server{})
// 启动服务
err := grpcServer.Serve(listen)
if err != nil {
fmt.Println(err)
}
}
// client
package main
import (
"context"
"fmt" pb "go-rpc/hello-server/proto"
"google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "log")
//type PerRPCCredentials interface {
// GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity() bool
//}
// 实现PerRPCCredentials
type ClientTokenAuth struct {
}
// 获取客户端提供的元数据
func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appId": "kelly",
"appKey": "123132",
}, nil
}
// 是否带安全认证
func (c ClientTokenAuth) RequireTransportSecurity() bool {
return false
}
func main() {
//creds, err := credentials.NewClientTLSFromFile("/Users/ayohuang/GolandProjects/go-rpc/key/test.pem", "*.kelly.com")
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))
c, err := grpc.NewClient("localhost:9090", opts...)
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 建立连接
client := pb.NewSayHelloClient(c)
// 执行rpc调用,在服务器端实现并返回结果
resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kelly"})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(resp.GetResponseMsg())
}
}
最后更新于