1.Releases · protocolbuffers/protobuf (github.com)下载对应环境和版本的Protocol Buffers
(下载之后将bin文件加入系统的环境变量Path中,在命令行中敲protoc命令检查安装是否成功)
2.安装grpc的核心库
go get google.golang.org/grpc
配合各个语言的代码生成工具,对于Golang
来说称为potoc-gen-go
需要安装两个库:
go install google.golang/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
在bin
目录下可以看到两个.exe
文件
说白了就是C/S交互的约束
//这是说明我们使用的是proto3语法
syntax="proto3";
//这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中,代表在当前目录生成,service代表了生成的go文件的包名是service
option go_package =".;server";
//然后我们需要定义一个服务,在这个服务中需要有一个方法,这个方法可以接受客户端的参数,再返回服务端的响应
//其实很容易可以看出,我们定义了一个Server,称为SayHello,这个服只有一个rpc方法,名为SayHello。
//这个方法会发送一个HelloRequest,然后返回一个HelloResponse。
service SayHello{
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
//message关键字,其实你可以理解为Golang中的结构体。
//这里比较特别的是变量后面的“赋值”。注意,这里并不是赋值,而是定义这个变量在这个message中的位置。
message HelloRequest{
string requestName = 1;
}
message HelloResponse{
string responseMsg = 1;
}
在编写完上面的内容后,在helloworld/proto目录下执行如下命令:
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
message
message:protobuf 中定义一个消息类型式是通过关键字message字段指定的。消息就是需要传输数据格式定义。
message 关键字类似于C++中的class,JAVA中的class,go中的struct。
在消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型
一个proto文件中可以定义多个消息类型
字段规则
required:消息中的必填字段,不设置回导致编码异常,在protobuf2中使用,在protobuf3中被删去
optional:消息体中的可选字段,protobuf3没有了required,optional等说明关键字,都默认为optional
repeate:消息体中可重复字段,重复的值的顺序会被保留着go中重复的回被定义为切片。
消息号
在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[1,2^29-1]范围内的一个整数。
嵌套信息
可以在其它消息类型中定义、使用消息类型、在下面的例子中,person消息就定义在Personinfo消息如·
message PersonInfo{
message Person{
string name = 1;
int32 height = 2;
repeated int32 weight = 3;
}
repeated Person info = 1;
}
如果要在它的父消息类型的外部重用这个消息类型,则需要Personinfo.Person的形式使用它,如:
message PersonMessage{
PersonInfo.Person info = 1;
}
服务定义
如果想要将消息类型用在PRC系统中,可以在==.proto文件中定义一个RPC服务接口==,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根
service SayHello{
# rpc 服务函数名(参数) 返回 (返回参数)
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
上述代码表示,定义了一个RPC服务,该方法接受HelloRequest返回HelloResponse
创建gRPC Server 对象,你可以理解为它是Server端的抽象对象
将server(其包含需要被调用的服务端接口)注册到gRPC Server的内部注册中心
这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
创建Listen,监听TCP端口
gRPC Server 开始 lis.Accept,知道Stop
package main
import (
"context"
"google.golang.org/grpc"
"log"
"net"
pb "xxb-grpc-study/hello-server/proto"
)
//hello server 服务器端
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{})
//启动服务
err := grpcServer.Serve(listen)
if err != nil {
log.Println("启动失败~")
}
}
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
pb "xxb-grpc-study/hello-client/proto"
)
func main() {
//WithTransportCredentials返回一个DialOption,用于配置连接级别的安全凭证(例如,TLS/SSL)。这不能与WithCredentialsBundle一起使用。
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Did not connect:%v", err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//rpc方法调用
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " newbie"})
fmt.Println(resp.GetResponseMsg())
}
gRPC 是一个典型的 C/S 模型,需要开发客户端和服务端,客户端与服务端需要达成协议,使用某一个确认的传输协议来传输数据,gRPC 通常默认是使用 protobuf 来作为传输协议,当然也是可以使用其他自定义的。
那么,客户端与服务端要通信之前,客户端如何知道自己的数据是发给哪一个明确的服务端呢?反过来,服务端是不是也需要有一种方式来弄个清楚自己的数据要返回给谁呢?
那么就不得不提 gRPC 的认证
此处说到的认证,不是用户的身份认证,而是指多个 server 和 多个 client 之间,如何识别对方是谁,并且可以安全的进行数据传输
客户端和服务端之间调用,我们可以通过加入证书的方式,实现调用的安全性
TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。
TLS协议主要解决如下三个网络安全问题。
生产环境可以购买证书或者使用一些平台发放的免费证书
key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到的数据解密。
csr:证书签名请求文件,用于提交给证书颁发机构(CA) 对证书签名。
crt:由证书颁发机构(CA) 签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签名者的签名等信息。
pem: 是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER。
首先通过openssl生成证书和私钥
1、官网下载: /source/index.html (openssl.org)
2、我们使用 便捷版安装包,一直下一步即可
3、配置环境变量把bin路径配置一下
4、命令行测试 openssl
生成证书
# 1、生成私钥
openssl genrsa -out server.key 2048
# 2、生成证书 全部回车即可,可以不填
openssl req -new -x509 -key server.key -out server.crt -days 3600
# 国家名称
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) []:
# 服务器or网站名称
Common Name (e.g. server FQDN or YOUR name) []:
# 邮件
Email Address []:
# 3、生成 csr
openssl req -new -key server.key -out server.csr
# 更改openssl.cnf
#1) 复制一份你安装的openssl的bin目录里面的openssl.cnf文件到你项目所在的目录
#2) 找到 [ CA_default ] 打开 copy_extensions = copy (就是把前面的#去掉)
#3)找到 [ req ] 打开 req_extensions = v3_req # The extensions to add to a certificate request
#4) 找到 [ v3_req ] 添加 subjectAltName = @alt_names
#5) 添加新的标签[ alt_name ] 和标签字段
#指定特定的网站才能访问
DNS.1 = *.newbie.com
#生成证书私钥test.key
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
更改后的server端
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
"net"
pb "xxb-grpc-study/hello-server/proto"
)
//hello server 服务器端
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:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.key")
//开启端口
listen, _ := net.Listen("tcp", ":9090")
//创建grpc服务
grpcServer := grpc.NewServer(grpc.Creds(creds))
//在grpc服务端中去注册我们自己编写的服务,注册一定是引用注册
pb.RegisterSayHelloServer(grpcServer, &server{})
//启动服务
err := grpcServer.Serve(listen)
if err != nil {
log.Println("启动失败~")
}
}
更改后的client端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
pb "xxb-grpc-study/hello-client/proto"
)
func main() {
creds, _ := credentials.NewClientTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "*.newbie.com")
//WithTransportCredentials返回一个DialOption,用于配置连接级别的安全凭证(例如,TLS/SSL)。这不能与WithCredentialsBundle一起使用。
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("Did not connect:%v", err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//rpc方法调用
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " newbie"})
fmt.Println(resp.GetResponseMsg())
}
Token认证
我们先看一个gRPC提供我们的一个接口,这个接口中由两个方法,接口位于credentials包下,这个接口需要客户端来实现
type PerRPCCredentials interface {
GetRequstMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
第一个方法作用是获取元数据信息,也就是客户端提供的key,value对,context用于控制超时和取消,uri是请求入口处uri
第二个方法的作用是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS验证,返回值是false则不用。
客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
pb "xxb-grpc-study/hello-client/proto"
)
type ClientTokenAuth struct {
}
func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appId": "newbie",
"appKey": "123123",
}, nil
}
func (c ClientTokenAuth) RequireTransportSecurity() bool {
return false
}
func main() {
//creds, _ := credentials.NewClientTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "*.newbie.com")
//WithTransportCredentials返回一个DialOption,用于配置连接级别的安全凭证(例如,TLS/SSL)。这不能与WithCredentialsBundle一起使用。
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))
conn, err := grpc.Dial("127.0.0.1:9090", opts...)
if err != nil {
log.Fatalf("Did not connect:%v", err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//rpc方法调用
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " newbie"})
fmt.Println(resp.GetResponseMsg())
}
服务端
package main
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"log"
"net"
pb "xxb-grpc-study/hello-server/proto"
)
//hello server 服务器端
type server struct {
pb.UnimplementedSayHelloServer
}
//方法重写
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 != "newbie" || appKey != "123123" {
return nil, errors.New("Token不正确")
}
fmt.Printf("hello" + req.RequestName)
return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}
func main() {
//从提供的根证书颁发机构证书文件构建TLS凭据,两个参数分别是自签名证书和私钥文件
//creds, _ := credentials.NewServerTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.key")
开启端口
listen, _ := net.Listen("tcp", ":9090")
//创建grpc服务
grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
//在grpc服务端中去注册我们自己编写的服务,注册一定是引用注册
pb.RegisterSayHelloServer(grpcServer, &server{})
//启动服务
err := grpcServer.Serve(listen)
if err != nil {
log.Println("启动失败~")
}
}
最后本文笔记来源于B站视频遇见狂神说,狂神老师
如果笔记存在任何问题欢迎各位大佬们的指出