官方中文文档
视频教学
视频教学2
什么是gRPC
概念:
序列化:将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所产生的二进制串转换成数据结构或者对象的过程
env :
grpc和protobuf
1 go get google.golang.org/grpc
protobuf配套go语言的代码生成工具,因为这些文件在安装grpc的时候已经下载下来了,所以使用install即可。
1 go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
1 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
1. proto文件编写
模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 syntax = "proto3" ;option go_package = ".;service" ;service SayHello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }message HelloRequest { string requestName = 1 ; }message HelloResponse { string responseMsg = 1 ; }
说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 syntax = "proto3" ;option go_package = ".;service" ;service SayHello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }message HelloRequest { string requestName = 1 ; }message HelloResponse { string responseMsg = 1 ; }
当前文件结构:
1 2 3 4 5 6 7 8 9 . ├── hello_client │ ├── main.go │ └── proto │ └── hello.proto └── hello_server ├── main.go └── proto └── hello.proto
在编写完上面的内容后,在helloworld/proto目录下执行如下命令:
1 protoc --go_out=. hello.proto
1 protoc --go-grpc_out=. hello.proto
执行命令后文件结构:
1 2 3 4 5 6 7 8 9 10 11 . ├── hello_client │ ├── main.go │ └── proto │ └── hello.proto └── hello_server ├── main.go └── proto ├── hello.pb.go ├── hello.proto └── hello_grpc.pb.go
2. proto文件介绍
message
message:protobuf中定义一个消息类型是通过关键字message字段指定的,消息就是需要传输的数据格式的定义 。
message关键字类似于C++中class,Java中的class,go中的struct
在消息体中承载的数据分别对应一个字段,其中每个字段都有一个名字和一种类型
一个proto文件中可以定义多个消息类型。
eg:
1 2 3 4 message HelloRequest { string requestName = 1 ; int64 age = 2 ; }
字段规则
require:消息体中必填的字段,不设置会导致编码异常。在protobuf2中使用,在protobuf中被删去。
optional:消息体中的可选字段。protobuf3没有了required,optional等说明关键字,都默认optional
eg:
1 2 3 4 message HelloRequest { string requestName = 1 ; int64 age = 2 ; }
repeat:消息体中可重复字段,重复的值的顺序会被保留,在go中重复的会被定义为切片。
eg:
1 2 3 4 5 message HelloRequest { string requestName = 1 ; int64 age = 2 ; repeated int32 weight = 3 }
消息号
在消息体中,每个字段都必须要有一个唯一的标识号,标识号是[1, 2^29-1]范围内的一个整数。
eg:
1 2 3 4 message HelloRequest { string requestName = 1 ; int64 age = 2 ; }
嵌套消息
可以在其他消息类型中定义、使用消息类型,在下面的例子中,person消息就定义在PersonInfo消息内:
1 2 3 4 5 6 7 8 message PersonInfo { message Person { string name = 1 ; int32 height = 2 ; repeated int32 weight = 3 } repeated Person info = 1 ; }
如果要在它的父消息类型的外部重用这个消息类型,需要PersonInfo.Person
的形式使用它。
服务定义
如果想要将消息类型用在RPC系统中,可以在 .proto 文件中定义一个RPC服务接口 ,protocal buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。
1 2 3 4 service SearchService { rpc Search(SearchRequest) returns (SearchResponse)}
上述代表表示,定义了一个RPC服务,该方法接受SearchRequest返回SearchResponse。
3. 服务端代码编写
创建gRPC Server对象,你可以理解为它是Server端的抽象对象
将server(其包含需要被调用的服务端口)注册到gRPC Server的内部注册中心。
这样可以在接受请求时,通过内部服务发现,发现该服务端接口并转接进行逻辑处理。
创建listen,监听TCP端口
gRPC Server 开始 lis.Accept,直到Stop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "context" "log" "net" pb "learning/grpc/hello_server/proto" "google.golang.org/grpc" )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 () { grpcServer := grpc.NewServer() pb.RegisterSayHelloServer(grpcServer, &server{}) listen, err := net.Listen("tcp" , ":9090" ) if err != nil { log.Fatalf("failed to listen: %v" , err) } err = grpcServer.Serve(listen) if err != nil { log.Fatalf("failed to serve: %v" , err) } }
4. 客户端代码编写
创建与给定目标(服务端)的连接交互
创建server的客户端对象
发送RPC请求,等待同步响应,得到回调后返回响应结果
输出响应结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "context" "fmt" "log" pb "learning/grpc/hello_server/proto" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" )func main () { conn, err := grpc.Dial("localhost:9090" , grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("did not connect: %v" , err) } defer conn.Close() client := pb.NewSayHelloClient(conn) resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "xxx" }) if err != nil { log.Fatalf("could not greet: %v" , err) } fmt.Println(resp.GetResponseMsg()) }
5. 认证-安全传输
gRPC是一个典型的C/S模型,需要开发客户端和服务器,客户端个服务器需要达成协议,使用某一个确认的传输协议来传输数据,gRPC通常默认使用protobuf来作为传输协议 ,当然也可以使用其他自定义的。
那么,客户端与服务端要通信之前,客户端如何知道自己的数据是发给哪一个明确的服务端呢?反过来,服务端是不是也需要有一种方式来弄清楚自己的数据要返回给谁呢?
那么就不得不提gRPC的认证
此处说到的认证,不是用户的身份认证,而是指多个server和多个client之间,如何识别对方是谁,并且可以安全的进行数据传输
SSL/TLS认证方式(采用http2协议)
基于Token的认证方式(基于安全连接)
不采用任何措施的连接,这是不安全的连接(默认采用http1)
自定义的身份认证
客户端和服务端之间调用,我们可以通过加入证书的方式,实现调用的安全性。
TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。
TLS协议主要解决如下三个网络安全问题。
1)保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
2)完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;
3)认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;
生产环境可以购买证书或者使用一些平台发放的免费证书
key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
csr:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
crt:由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
pem:是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER.
聊聊HTTPS和SSL/TLS协议
6. SSL/TSL认证方式
首先通过openssl生成证书和私钥(mac自带,或者homebrew安装)
官网:https://www.openssl.org/source
生成证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #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 FQDNor YOUR name) []:kuangstudy #邮件 Email Address[]:24736743@qq.com #3、生成 csr openssl req -new -key server.key -out server.csr
生成私钥,可以在项目根目录下新建一个key目录,用来存放安全文件,在这个目录下执行第一条命令。
目录结构变为:
1 2 3 4 5 6 7 8 9 10 11 12 13 . ├── hello_client │ ├── main.go │ └── proto │ └── hello.proto ├── hello_server │ ├── main.go │ └── proto │ ├── hello.pb.go │ ├── hello.proto │ └── hello_grpc.pb.go └── key └── server.key
在这个目录下执行第二条命令,全部回车。
1 2 3 . ├── server.crt └── server.key
在这个目录下执行第二条命令,全部回车。
1 2 3 4 . ├── server.crt ├── server.csr └── server.key
在终端中运行以下命令,可以帮助查找 OpenSSL 配置文件的默认路径:
复制配置文件到key目录下
1 2 3 4 5 . ├── openssl.cnf ├── server.crt ├── server.csr └── server.key
修改openssl.cnf:
1 2 3 4 5 6 #1)找到[CA_default],打开copy_extensions=copy(就是把前面的#去掉) #2)找到[req],打开 req_extensions =v3_req #The extensions to add to a certificate request #3)找到[v3_req],添加subjectAltName = @alt_names #4)添加新的标签[alt_names],和标签字段: [alt_names] DNS.1 = *.kuangstudy.com
生成证书私钥test.key
1 openssl genpkey -algorithm RSA -out test.key
1 2 3 4 5 6 . ├── openssl.cnf ├── server.crt ├── server.csr ├── server.key └── test.key
通过私钥test.key生成证书请求文件test.csr(注意cfg和cnf)
1 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
1 2 3 4 5 6 7 . ├── openssl.cnf ├── server.crt ├── server.csr ├── server.key ├── test.csr └── test.key
test.csr是上面生成的证书请求文件。ca.crt/server.key是CA证书文件和key,用来对test.csr进行签名认证。这两个文件在第一部分生.
生成SAN证书 pem
1 openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
1 2 3 4 5 6 7 8 9 . ├── openssl.cnf ├── server.crt ├── server.csr ├── server.key ├── server.srl ├── test.csr ├── test.key └── test.pem
修改hello_server/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "context" "google.golang.org/grpc/credentials" "log" "net" pb "learning/grpc/hello_server/proto" "google.golang.org/grpc" )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 () { creds, _ := credentials.NewServerTLSFromFile("../key/test.pem" , "../key/test.key" ) grpcServer := grpc.NewServer(grpc.Creds(creds)) pb.RegisterSayHelloServer(grpcServer, &server{}) listen, err := net.Listen("tcp" , ":9090" ) if err != nil { log.Fatalf("failed to listen: %v" , err) } err = grpcServer.Serve(listen) if err != nil { log.Fatalf("failed to serve: %v" , err) } }
修改hello_client/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport ( "context" "fmt" "google.golang.org/grpc/credentials" "log" pb "learning/grpc/hello_server/proto" "google.golang.org/grpc" )func main () { creds, _ := credentials.NewClientTLSFromFile("../key/test.pem" , "*.kuangstudy.com" ) conn, err := grpc.Dial("localhost:9090" , grpc.WithTransportCredentials(creds)) if err != nil { log.Fatalf("did not connect: %v" , err) } defer conn.Close() client := pb.NewSayHelloClient(conn) resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "xxx" }) if err != nil { log.Fatalf("could not greet: %v" , err) } fmt.Println(resp.GetResponseMsg()) }
7. 自定义token认证(pending)
8. 流式gRPC(pending)
9. 错误处理(pending)