笔记目录
基本结构
目录结构
.
├── go.mod
├── go.sum
├── hello-client
│ ├── main.go
│ └── proto
│ └── hello.proto
└── hello-server
├── main.go
└── proto
├── gen.sh
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
工具准备
安装 Protobuf
- 下载 protobuf: https://github.com/protocolbuffers/protobuf/releases
下载并解压到:/tmp/protobuf
- 配置环境变量
vim /etc/profile
export PATH=$PATH:/tmp/protobuf/bin
# 或者
cp /tmp/protobuf/bin/protoc /usr/local/bin
- 安装
gRPC
核心库
go get google.golang.org/grpc
protocol
编译器,可以生成不同语言的代码
- 安装
# 这条语句把 go install 安装的包的所在可执行目录加入到环境变量
# 不然使用 go install 后找不到安装的包
export PATH=$PATH:/root/go/bin
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
客户端代码编写
main.go
package main
import (
"context"
"fmt"
"log"
"time"
pb "xxx-grpc-demo/hello-server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
start := time.Now() // 记录开始时间
cc, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer cc.Close()
// 建立连接
client := pb.NewSayHelloClient(cc)
// 执行 rpc 调用(这个方法在服务器端来实现并返回结果)
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kuangshen"})
fmt.Println(resp.GetResponseMsg())
elapsed := time.Since(start) // 计算经过的时间
fmt.Println("程序运行耗时:", elapsed)
}
服务端代码
main.go
package main
import (
"context"
"fmt"
"net"
pb "xxx-grpc-demo/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) {
fmt.Printf("hello" + req.RequestName)
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 {
fmt.Printf("failed to serve: %v", err)
return
}
}
hello.proto 文件
// 使用proto3语法
syntax = "proto3";
// 生成的go文件在哪个目录哪个包中, . 表示在当前目录生成,service 代表生成的go文件的包名是service
option go_package = ".;service";
// 定义服务,其中有个方法,接收客户端参数,返回服务端响应
service SayHello {
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
// 对应golang里面的结构体
// 这里面的 “赋值” 代表这个变量在这个 message 中的位置
message HelloRequest {
string requestName = 1;
}
message HelloResponse {
string responseMsg = 1;
}
gen.sh 编写
代码生成脚本
#!/bin/bash
# 这两条指令都用于生成用于Go语言的Protocol Buffers代码。
# - `protoc --go_out=. hello.proto`:这条指令使用`--go_out`插件,
# 它会生成与`hello.proto`文件中定义的消息类型和服务相关的Go代码。
# 生成的代码包含用于序列化和反序列化消息的方法,以及用于访问和操作消息字段的方法。
# - `protoc --go-grpc_out=. hello.proto`:这条指令使用`--go-grpc_out`插件,
# 它会生成与`hello.proto`文件中定义的gRPC服务相关的Go代码。
# 生成的代码包含用于实现gRPC服务接口的方法,以及用于创建gRPC客户端和服务器的辅助函数。
# 总结来说,`--go_out`生成的代码用于处理消息的序列化和反序列化,
# 而`--go-grpc_out`生成的代码用于实现和使用gRPC服务。
# 如果您只需要处理消息的序列化和反序列化,那么只需使用`--go_out`即可。
# 如果您还要使用gRPC服务,那么需要同时使用`--go_out`和`--go-grpc_out`。
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
SSL/TSL 认证
sudo apt update
sudo apt install build-essential checkinstall zlib1g-dev -y
cd /usr/local/src/
sudo wget https://www.openssl.org/source/openssl-1.1.1b.tar.gz
sudo tar -xf openssl-1.1.1b.tar.gz
cd openssl-1.1.1b
sudo ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib
sudo make
sudo make test
注意:
- prefix和–openssldir =设置OpenSSL的输出路径。
- shared = force来创建共享库。
- zlib =使用zlib库启用压缩。
make install
echo "/usr/local/ssl/lib" >> /etc/ld.so.conf.d/openss1-1.1.1b.conf
ldconfig -v
sudo mv /usr/bin/c_rehash /usr/bin/c_rehash.BEKUP
sudo mv /usr/bin/openssl /usr/bin/openssl.BEKUP
vim /etc/environment
# 加入
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/ssl/bin"
source /etc/environment
echo $PATH
openssl version -a
apt-get install libssl-dev
- 生成证书
# 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 FQDN or YOUR name) []:kuangstudy
# 邮件
Email Address []:2473674@qq.com
# 3、生成 csr
openssl req -new -key server.key -out server.csr
# 更改 openssl.cnf (linux 是 openssl.cfg)
# 1) 复制一份你安装的 openssl 的 bin 目录里面的 openssl.cnf 文件到你项目所在的目录
# linux: /usr/local/ssl/openssl.cnf
# cp /usr/local/ssl/openssl.cnf dst
# 2) 找到 [ CA_default ] 打开 copy_extensions = copy (去掉前面的 # )
# 3) 找到 [ req ] 打开 req_extensions = v3_req (去掉前面的 # )
# 4) 找到 [ v3_req ] 添加 subjectAltName = @alt_names
# 5) 添加新的标签 [ alt_names ],和标签字段
DNS.1 = *.kuangstudy.com
# 生成证书私钥
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
目录结构
.
├── go.mod
├── go.sum
├── hello-client
│ ├── main.go
│ └── proto
│ └── hello.proto
├── hello-server
│ ├── main.go
│ └── proto
│ ├── gen.sh
│ ├── hello.pb.go
│ ├── hello.proto
│ └── hello_grpc.pb.go
└── key
├── openssl.cnf
├── server.crt
├── server.csr
├── server.key
├── server.srl
├── test.csr
├── test.key # 私钥
└── test.pem # 证书
服务端代码
package main
import (
"context"
"fmt"
"net"
pb "xxx-grpc-demo/hello-server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
type server struct {
pb.UnimplementedSayHelloServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
fmt.Printf("hello" + req.RequestName)
return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}
func main() {
creds, _ := credentials.NewServerTLSFromFile("/root/Project/grpc-demo/key/test.pem", "/root/Project/grpc-demo/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 {
fmt.Printf("failed to serve: %v", err)
return
}
}
客户端代码
package main
import (
"context"
"fmt"
"log"
"time"
pb "xxx-grpc-demo/hello-server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
creds, _ := credentials.NewClientTLSFromFile("/root/Project/grpc-demo/key/test.pem", "*.kuangstudy.com")
start := time.Now() // 记录开始时间
cc, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer cc.Close()
// 建立连接
client := pb.NewSayHelloClient(cc)
// 执行 rpc 调用(这个方法在服务器端来实现并返回结果)
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kuangshen"})
fmt.Println(resp.GetResponseMsg())
elapsed := time.Since(start) // 计算经过的时间
fmt.Println("程序运行耗时:", elapsed)
}
Token 认证
gRPC 提供了一个接口,位于credentials
包下,需要客户端来实现
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
- 第一个方法作用是获取元数据信息,也就是客户端提供的
key:value
对,context
用于控制超时和取消,uri
是请求入口处的uri
- 第二个方法的作用是是否需要基于
TLS
认证进行安全传输,如果返回值是true
,则必须加上TLS
验证,返回值是false
则不用
服务端代码
package main
import (
"context"
"errors"
"fmt"
"net"
pb "xxx-grpc-demo/hello-server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
)
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]
fmt.Println("appID:", appId)
}
if v, ok := md["appkey"]; ok {
appKey = v[0]
fmt.Println("appKey:", appKey)
}
if appId != "kuangshen" || appKey != "123123" {
return nil, errors.New("token 不正确")
}
fmt.Printf("hello" + req.RequestName)
return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}
func main() {
// 开启端口
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 {
fmt.Printf("failed to serve: %v", err)
return
}
}
客户端代码
package main
import (
"context"
"fmt"
"log"
"time"
pb "xxx-grpc-demo/hello-server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
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
}
func main() {
start := time.Now() // 记录开始时间
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))
cc, err := grpc.Dial("127.0.0.1:9090", opts...)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer cc.Close()
// 建立连接
client := pb.NewSayHelloClient(cc)
// 执行 rpc 调用(这个方法在服务器端来实现并返回结果)
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "kuangshen"})
fmt.Println(resp.GetResponseMsg())
elapsed := time.Since(start) // 计算经过的时间
fmt.Println("程序运行耗时:", elapsed)
}