RPC
(Remootee Procceduure Calls) 远程过程调用
是一种协议,用来屏蔽分布式计算机中的各种调用细节,使得可以像是本地调用一样直接调用一个远程函数
为什么需要RPC?
一般的:
客户端 与 服务端 沟通的过程
- 客户端发送数据(以字节流的方式)
- 服务端接受并解析。把结果返回给客户
RPC:
- RPC就是将上述过程封装,使其操作更加优化
- 使用一些大家都认可的协议,使其规范化
- 做成一些框架,直接或间接产生利益
GRPC
GRPC就是一个高性能的、开源的、通用的RPC框架
grpc使用了Protocol Buffss.这是谷歌开源的一套成熟的数据结构序列化机制。
- 序列化:把数据结构或对象转成二进制串的过程
- 反序列化:将在序列化过程中所产生的二进制串转成数据结构或对象的过程
安装Protobuf
1. 安装protocol buffers
下载win安装包、解压到本地、添加环境变量。
测试:cmd输入protoc
2. 安装gRPC核心库
go get -u google.goland.org/grpc
3. 安装go protocol buffers的插件protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest`
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest`
Proto文件
运行
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
文件编写
syntax = "proto3";
option go_package = ".;service";
service SayHello{
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest{
string requestName = 1;
}
message HelloResponse{
string responseMsg = 1;
}
- message 数据格式的定义,类似结构体
- 字段规则
- required 必填字段 不设置会导致编码异常 protobuf3被删去
- optional 可选字段 默认
- repeated 可重复字段 go中会被定义为切片
- 消息号 在消息体的定义中,每个字段都必须有一个唯一的标识号
消息体是可以嵌套使用的
message PersonInfo{
message Person{
string name = 1;
int32 height = 2;
repeated int32 weight = 3;
}
repeated Person info = 1;
}
message PersonMessage{
PersonInfo.Person info = 1;
}
- 服务定义 相当于函数
service SercjService{
# rpc 服务函数名(参数) 返回 (返回参数){}
rpc Search(SearchResquest) return (SearchResponse) {}
}
服务端编写
package main
import (
"context"
pb "douyin/server/proto"
"google.golang.org/grpc"
"net"
)
type server struct {
pb.UnimplementedSayHelloServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{ResponseMsg: "Hello" + req.RequestName}, nil
}
func main() {
// 开启端口
listen, _ := net.Listen("tcp", ":9090")
//创建GRPC服务
grpcServer := grpc.NewServer()
// 再grpc服务端注册自己编写的服务
pb.RegisterSayHelloServer(grpcServer, &server{})
// 启动服务
_ = grpcServer.Serve(listen)
}
客户端编写
package main
import (
"context"
pb "douyin/server/proto"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
)
func main() {
//连接到server端
// 此处禁用安全认证
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//执行rpc调用(这个方法是在服务端实现并返回结果
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "LXJ "})
fmt.Println(resp.GetResponseMsg())
}
认证、安全传输
默认使用protobuf作为传输协议 当然也可以自定义其他协议。
SSL/TLS认证方式
环境配置
配置环境变量
测试openssl
生成证书
# 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 server.csr
1. 复制bin中的openssl.cnf到key
2. 找到[CA_default] 取消注释 copy_extensions = copy
3. 找到[req] 取消注释 req_extensions = v3_req
4. 找到[v3_req] 添加 subjectAltName = @alt_names
5. 添加新的标签[alt_names] 和 标签字段 DNS.1 = *.Gangnam.com
# 生成证书私钥test.key
openssl genpkey -algorithm RSA -out test.key
# 通过私钥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.cfg -extensions v3_req
# 生成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
server端代码
package main
import (
"context"
pb "douyin/server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"net"
)
type server struct {
pb.UnimplementedSayHelloServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{ResponseMsg: "Hello" + req.RequestName}, nil
}
func main() {
//TLS认证
creds, _ := credentials.NewServerTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
"D:\\GoProjects\\douyin\\key\\test.key")
// 开启端口
listen, _ := net.Listen("tcp", ":9090")
//创建GRPC服务
grpcServer := grpc.NewServer(grpc.Creds(creds))
// 再grpc服务端注册自己编写的服务
pb.RegisterSayHelloServer(grpcServer, &server{})
// 启动服务
_ = grpcServer.Serve(listen)
}
client端代码
package main
import (
"context"
pb "douyin/server/proto"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
)
func main() {
file, _ := credentials.NewClientTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
"*.Gangnam.com")
//连接到server端
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(file))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//执行rpc调用(这个方法是在服务端实现并返回结果
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "LXJ "})
fmt.Println(resp.GetResponseMsg())
}
Token认证方式
type PerRPCCredentials interface {
// GetRequestMetadata gets the current request metadata, refreshing tokens
// if required. This should be called by the transport layer on each
// request, and the data should be populated in headers or other
// context. If a status code is returned, it will be used as the status for
// the RPC (restricted to an allowable set of codes as defined by gRFC
// A54). uri is the URI of the entry point for the request. When supported
// by the underlying implementation, ctx can be used for timeout and
// cancellation. Additionally, RequestInfo data will be available via ctx
// to this call. TODO(zhaoq): Define the set of the qualified keys instead
// of leaving it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity indicates whether the credentials requires
// transport security.
RequireTransportSecurity() bool
}
第一个方法的作用是获取元数据信息,也就是客户端提供(key valued)对, context用于控制超时和取消,uri是请求入口处的uri
第二个方法的作用是是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS验证,false则不需要
Client端代码
package main
import (
"context"
pb "douyin/server/proto"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
)
type ClientTokenAuth struct {
}
func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appId": "lxj",
"appKey": "123",
}, nil
}
func (c ClientTokenAuth) RequireTransportSecurity() bool {
return false // 不开启安全认证
}
func main() {
//file, _ := credentials.NewClientTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
// "*.Gangnam.com")
//连接到server端
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) // 无安全认证 上面是false
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))
conn, err := grpc.Dial("127.0.0.1:9090", opts...)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//执行rpc调用(这个方法是在服务端实现并返回结果
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "LXJ "})
fmt.Println(resp.GetResponseMsg())
}
Server端代码
package main
import (
"context"
pb "douyin/server/proto"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"net"
)
type server struct {
pb.UnimplementedSayHelloServer
}
// SayHello 业务
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, 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]
}
//校验
if appId != "lxj" || appKey != "123" {
return nil, errors.New("Token 错误")
}
fmt.Println("hello " + req.RequestName)
return &pb.HelloResponse{ResponseMsg: "Hello" + req.RequestName}, nil
}
func main() {
//TLS认证
//creds, _ := credentials.NewServerTLSFromFile("D:\\GoProjects\\douyin\\key\\test.pem",
// "D:\\GoProjects\\douyin\\key\\test.key")
// 开启端口
listen, _ := net.Listen("tcp", ":9090")
//创建GRPC服务
grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
// 再grpc服务端注册自己编写的服务
pb.RegisterSayHelloServer(grpcServer, &server{})
// 启动服务
_ = grpcServer.Serve(listen)
}