目录
grpc全指南
什么是grpc
grpc使用场景
grpc的模式
如何安装
定义proto文件
简单模式代码实现
简单模式运行效果
双向数据流代码实现
双向数据流运行效果
参考文档:
Grpc 看到百度上口水话极多,大多都是互相抄袭,今儿我直接教大家先用起来,你们自己再去看文档关于 grpc 的其他特性和玩法。
go-rpc 简写就是 grpc,是 go 语言实现的 rpc 远程过程调用框架,http 协议也是 RPC 的一种实现,grpc 是一个高性能、开源和拥有统一规定的 RPC 框架,面向对象的 http/2 通信协议,能够能节省空间和 IO 密集度的开销。
既然有 http 通信协议,有 json 这种易读的传输格式,为啥还需要 grpc 呢?首先,grpc 可以建立长连接,且不同于 websocket,grpc 是不依赖于浏览器的客户端与服务端来通信,多路复用让大并发都在一个流上跑。二进制格式传输,虽然人不可读但对于机器来说十分友好,header 头的压缩,传输效率大幅提高。从以上特点就可以看出,grpc 用于高效传输,减少多次握手与挥手的开销,当然 grpc 的好处不限上述内容,还不会用 grpc,知道那么多好处有什么用呢,如果想了解更多 grpc 的好处,请看官方文档 https://grpc.io/docs/
4 种模式使用与场景:
1、简单模式 客户端发起一次请求,服务端响应一次请求,标准 RPC 通信
2、客户端数据流 客户端发送多次请求给服务端,服务端在客户端发送完毕后,服务端响应一次;如搜集用户信息,客户端向服务端多次发送数据,服务端做处理并返回一个最终的结果,减少客户端多次连接服务端的开销。
3、服务端数据流 客户端发起一次请求,服务端不断的返回连续的数据流;比如我司项目中需要不断获取某区域点位变化情况,从而实时展现给用户数据,这时候客户端发送一次区域信息,服务端不断返回数据
4、双向数据流 这时候客户端和服务端都建立起长连接,同时互相发送数据,实现实时交互。
跟着我写的做,我不信你跑不起来 GRPC:
我以官方截至2021年3月31日最新内容给大家做展示例子:
因为 grpc 是基于 IDL 文件定义服务,所以需要用 proto3 工具来生成咱们 go 语言用的代码,所以首先下载工具 protoc
*(1)、下载 protoc * https://github.com/protocolbuffers/protobuf/releases 下载自己对应系统的可执行文件,我是 windows 的,所以下载protoc-3.15.6-win64.zip
(2)、 通过 go get 获取 protoc-gen-go 与 protoc-gen-go-grpc
go get google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
以上三个文件 protoc.exe protoc-gen-go.exe protoc-gen-go-grpc.exe 三个文件均放入 go 的 bin 目录下,并把 bin 目录加入系统环境变量
之后稍微验证一下:
PS C:\Users\anyanfei\Desktop> protoc --version
libprotoc 3.15.6
由于我个人特别喜欢谢大的 beego 框架,所以当前目录以 beego2.0 作为基础,项目就叫 beego20 了:
beego20
|_conf
|_controllers
|_grpc
|_protoFile
|_Chat.proto
|_service
|_Chat.pb.go
|_Chat_grpc.pb.go
|_models
|_routers
|_tests
go.mod
main.go
从目录中可以看到,我自己创建了一个名为 grpc 的文件夹,然后在这下面创建了一个叫 protoFile 和 service 的两个文件夹,其中我们先在 protoFile 的根目录下创建一个名为 Chat.proto 文件,里面的内容是这样的,我会一段一段的做注释:
syntax = "proto3"; // 使用protocol buffer proto3 语法
package chat; //定义一个包名
option go_package = "../service"; //定义需要自动生成的go文件 我现在想放在service文件夹下
//请求数据 Request格式定义 结构数据定义从1开始,如果是枚举类型,字段要从0开始
message Request {
string name = 1;
}
//响应数据 Response格式定义 同上
message Response{
string message = 1;
}
/*
定义一个服务名:Chat
其中只有名为AutoResponse的一个RPC服务,当然你也可以定义多个,可以再在这里面写一个rpc AutoResponse2(Request) returns (Response){}
输入是Request格式的内容,输出是Response格式的内容
*/
service Chat{
rpc AutoResponse(Request) returns (Response){}
}
以上就定义了一个简单模式,如果要定义个双向数据流模式,可以更改服务内的代码为:(当然第一个例子我们还是以简单模式来做)
rpc AutoResponse(stream Request) returns (stream Response){}
定义好了文件后,就需要开始生成我们所需要的代码:
就在上面创建 Chat.proto 文件目录下执行以下代码
protoc --go_out=. ./Chat.proto
protoc --go-grpc_out=. ./Chat.proto
我解释一下上面两句话分别做了什么事儿:
protoc --go_out=. ./Chat.proto 执行后会产生名为Chat.pb.go文件,这个文件里面包含用于填充、序列化 和 检索请求和响应消息类型的所有协议的代码。
protoc --go-grpc_out=. ./Chat.proto 执行后会产生名为Chat_grp.pb.go文件,这个文件中包含的内容有:
1、客户端使用Chat服务中定义的方法(AutoResponse)调用的接口类型,我们只需要调用这个方法即可。
2、服务端要实现的接口类型(AutoResponse),我们需要实现这个方法。
我知道大家开始发懵了,别急,接下来,我会慢慢用代码来实现,并逐行解释
我们在 Chat_grpc.pb.go 文件中找到 下面接口代码的所在位置,其中 ChatServer 是我们在 proto 文件中定义的 service Chat
type ChatServer interface {
AutoResponse(context.Context, *Request) (*Response, error)
mustEmbedUnimplementedChatServer()
}
要实现一个接口,在 golang 中只需要实现他所有的方法即可,为了方便展示,我将方法写在了 main.go 里:
package main
import (
pb "beego20/grpc/service" //这里是我们引入生成文件的包,并取了一个叫pb的别名
_ "beego20/routers"
"context"
beego "github.com/beego/beego/v2/server/web"
"google.golang.org/grpc" //这里是引入grpc框架库
"log"
"net"
)
type server struct{ //创建一个结构体
pb.UnimplementedChatServer //实现接口ChatServer interface的方法
}
func(s *server) AutoResponse(ctx context.Context, req *pb.Request) (*pb.Response, error){ //实现服务端收到客户端请求后的应答方法
log.Println("客户端传来:",req.GetName())
return &pb.Response{Message: "服务端应答---->客户端传来的内容:" + req.GetName()},nil //返回给客户端的应答内容 req.GetName()就是客户端发来的名字,也就是我们在proto文件中定义Request中的 string name = 1;
}
func listenGRPC(){ //创建一个监听GRPC的方法
lis , err := net.Listen("tcp",":8001") //监听一个tcp服务,端口为8001
if err !=nil{
log.Fatalln("监听端口失败:",err)
}
s := grpc.NewServer() // NewServer创建一个gRPC服务,虽然还未注册,但已经开始接受请求了。返回一个用于服务RPC请求的gRPC服务。
pb.RegisterChatServer(s,&server{}) //将我们的服务注册到rpc里
if err = s.Serve(lis); err !=nil{ //开始启动GRPC服务
log.Fatalln("启动服务失败:",err)
}
}
func main() {
go listenGRPC() //给grpc开一个单独的协程,以便能让beego的http服务也能够正常启动
beego.Run()
}
我们开始开启 GRPC 服务端:go run main.go
运行服务端的时候可能会遇到以下问题:
# beego20/grpc/service
grpc\service\Chat_grpc.pb.go:15:11: undefined: grpc.SupportPackageIsVersion7
grpc\service\Chat_grpc.pb.go:25:5: undefined: grpc.ClientConnInterface
grpc\service\Chat_grpc.pb.go:28:23: undefined: grpc.ClientConnInterface
grpc\service\Chat_grpc.pb.go:65:27: undefined: grpc.ServiceRegistrar
不要看百度的回答,什么给 protoc-gen-go 降级,官方都建议我们升级最新的,以便获得更高效更稳定的体验,所以我们直接进 undefined: grpc.SupportPackageIsVersion7 查看:
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. //这里专门说了,需要GRPC-GO在1.32.0版本及其以上,官方早就预料到可能go-grpc版本太低
const _ = grpc.SupportPackageIsVersion7
我们现在去 go.mod 中修改我们的 grpc 版本
module beego20
go 1.16
require github.com/beego/beego/v2 v2.0.1
require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/smartystreets/goconvey v1.6.4
google.golang.org/grpc v1.26.0
google.golang.org/protobuf v1.26.0
)
我发现我的 grpc 版本才 v1.26.0,那么将它改为 v1.32.0 👆👆👆,之后再运行自动解决依赖 go mod tidy
再次启动服务端,正常开启 grpc 服务与 beego 的 http 服务:
2021/04/02 13:57:44.494 [I] [parser.go:85] E:\code\go\src\beego20\controllers no changed
2021/04/02 13:57:44.515 [I] [server.go:241] http server Running on http://:8080
至此,服务端启动完毕,开始写简单模式下客户端的代码:
我在 grpc 文件夹下创建了一个叫 client 的文件夹,然后在 client 文件夹下创建了一个 main.go 文件作为例子:
package main
import (
pb "beego20/grpc/service" //和服务端一样,引入生成文件的包,还是取了一个叫pb的别名
"context"
"google.golang.org/grpc" //客户端也同样需要grpc框架
"log"
"os"
"time"
)
func main(){
conn , err := grpc.Dial(":8001",grpc.WithInsecure(),grpc.WithBlock()) // 用grpc去连接服务端开放的8001端口,后面两个参数grpc.WithInsecure()设置后不需要传入证书,grpc.WithBlock()参数让客户端进入连接状态返回连接套接字
//这里是需要证书的情况:
//tlsInsecure, _ := credentials.NewClientTLSFromFile("xxx.crt", “服务器名”)
//conn , err := grpc.Dial(":8001",grpc.WithTransportCredentials(tlsInsecure))
if err !=nil{
log.Fatalln("客户端连接不到",err)
}
defer conn.Close()
c := pb.NewChatClient(conn) //调用方法创建一个客户端连接
ctx , cancel := context.WithTimeout(context.Background(),time.Second) //设置上下文超时取消
defer cancel()
resp , err := c.AutoResponse(ctx,&pb.Request{ //开始向服务端发送数据,数据来源是cmd命令行输入的内容
Name: os.Args[1],
})
if err !=nil{
log.Fatalln("没有得到响应",err)
}
log.Println(resp.GetMessage())
}
刚才咱们已经启动了服务端,现在开始启动客户端,并传入我的名字,客户端用 os.Args[1] 简单获取,在 cmd 命令行下输入:
E:\code\go\src\beego20\grpc\client>go run main.go anyanfei
2021/04/02 14:14:47 服务端应答---->客户端传来的内容:anyanfei
服务端则会显示:
2021/04/02 14:14:42.553 [I] [parser.go:85] E:\code\go\src\beego20\controllers no changed
2021/04/02 14:14:42.579 [I] [server.go:241] http server Running on http://:8080
2021/04/02 14:14:47 客户端传来: anyanfei
你已经学会简单模式下的 GRPC 使用了,实际上写多了你就会自己封装一个连接方法了,就那么个套路而已
接下来,我要变形啦~直接来一个双向数据流模式的客户端与服务端代码,自己对着打一次,运行一下,就明白了
AutoRobot.proto 文件:
syntax = "proto3"; // 使用protocol buffer proto3 语法
package robot;//包名
option go_package = "../service";
//请求数据 Request格式定义
message Request {
string input = 1;
}
//响应数据 Response格式定义
message Response{
string output = 1;
}
/*
服务名:RobotChat
其中只有名为AutoResponse的一个RPC服务,
输入是Request格式的数据流,输出是Response格式的数据流
*/
service RobotChat{
rpc ChatRpc(stream Request) returns (stream Response){}
}
服务端实现:
package main
import (
pb "beego20/grpc/service"
_ "beego20/routers"
beego "github.com/beego/beego/v2/server/web"
"google.golang.org/grpc"
"io"
"log"
"net"
)
type server struct{
pb.UnimplementedRobotChatServer
}
func (s server) ChatRpc(serverStream pb.RobotChat_ChatRpcServer) error{
ctx := serverStream.Context()
for{
select {
case <- ctx.Done():
log.Println("收到客户端断开连接的信号")
return ctx.Err()
default:
req, err := serverStream.Recv()
if err == io.EOF{
log.Println("客户端发出数据流结束")
return nil
}
if err !=nil{
log.Println("服务端接收错误",err)
return err
}
switch req.Input {
case "Exit":
log.Println("服务端收到了'Exit'指令,客户端已退出")
if err = serverStream.Send(&pb.Response{
Output: "服务端收到了您的退出指令",
});err !=nil{
log.Println("返回给客户端数据出错",err)
return err
}
return nil
default:
if err = serverStream.Send(&pb.Response{
Output: "【服务端返回】:你好," + req.Input,
});err !=nil{
log.Println("返回给客户端数据出错",err)
return err
}
}
}
}
}
func listenGRPC(){ //创建一个监听GRPC的方法
lis , err := net.Listen("tcp",":8001") //监听一个tcp服务,端口为8001
if err !=nil{
log.Fatalln("监听端口失败:",err)
}
s := grpc.NewServer() // NewServer创建一个gRPC服务,虽然还未注册,但已经开始接受请求了。返回一个用于服务RPC请求的gRPC服务。
pb.RegisterRobotChatServer(s,&server{}) //将我们的服务注册到rpc里
if err = s.Serve(lis); err !=nil{ //开始启动GRPC服务
log.Fatalln("启动服务失败:",err)
}
}
func main() {
go listenGRPC()
beego.Run()
}
客户端实现:
package main
import (
pb "beego20/grpc/service"
"bufio"
"context"
"fmt"
"google.golang.org/grpc"
"io"
"log"
"os"
)
func main(){
conn, err := grpc.Dial(":8001",grpc.WithInsecure(),grpc.WithBlock())
if err !=nil{
log.Println("连接客户端失败",err)
return
}
defer conn.Close()
c := pb.NewRobotChatClient(conn)
clientStream , err := c.ChatRpc(context.Background())
if err !=nil{
log.Println("创建客户端数据流失败",err)
return
}
go func() {
fmt.Println("请输入信息:")
reader := bufio.NewReader(os.Stdin)
for {
cmdString , _ , _ := reader.ReadLine()
if err = clientStream.Send(&pb.Request{
Input: string(cmdString),
});err !=nil{
log.Println("发送给服务端发生错误")
os.Exit(1)
}
}
}()
for {
resp , err := clientStream.Recv()
if err !=nil{
log.Println("服务端已退出",err)
break
}
if err == io.EOF{
log.Println("收到服务端结束信号")
break
}
fmt.Println("【客户端收到响应】:",resp.Output)
}
}
直接运行服务端,然后运行客户端 go run main.go
客户端内容展示:
PS E:\code\go\src\beego20\grpc\client> go run .\main.go
请输入信息:
anyanfei
【客户端收到响应】: 【服务端返回】:你好,anyanfei
我叫春卷虎
【客户端收到响应】: 【服务端返回】:你好,我叫春卷虎
Exit
【客户端收到响应】: 服务端收到了您的退出指令
2021/04/02 16:24:40 服务端已退出 EOF
服务端内容展示:
2021/04/02 16:23:18.245 [I] [parser.go:85] E:\code\go\src\beego20\controllers no changed
2021/04/02 16:23:18.266 [I] [server.go:241] http server Running on http://:8080
2021/04/02 16:24:40 服务端收到了'Exit'指令,客户端已退出