文章目录
gRPC学习笔记
一、gRPC介绍
gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ErO385KK-1640622187958)(C:\Users\v_rwsnowguo\Desktop\图库\pic\2021\12\Snipaste_2021-12-03_16-09-45.png)]
1.1 gRPC vs. Restful API
gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说, gRPC使用的http2.0,而restful api则不一定)。不过gRPC还是有些特有的优势,如下:
- gRPC可以通过protobuf来定义接口,从而可以有更加严格的接口约束条件。
- 通过protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。
- gRPC可以方便地支持流式通信(理论上通过http2.0就可以使用streaming模式, 但是通常web服务的restful api似乎很少这么用,通常的流式数据应用如视频流,一般都会使用专门的协议如HLS,RTMP等,这些就不是我们通常web服务了,而是有专门的服务器应用。)
1.2 四类服务方法
-
单项RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
rpc SayHello(HelloRequest) returns (HelloResponse);
-
服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
-
客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
-
双向流式 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
二、Protobuf
Protobuf是由google开发的一套开源序列化协议框架,类似于XML,JSON,采用协议序列化用于数据存储与读取,与XML相比,定义了数据格式更加简单,数据访问接口可以自动化生成,加快了开发者开发速度
2.1 安装
win安装
pip install grpcio-tools
到https://github.com/protocolbuffers/protobuf/releases下载对应的安装包,并将protoc.exe放在PATH下
ubuntu安装
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
到https://github.com/protocolbuffers/protobuf/releases下载对应的安装包,并将protoc放在PATH下
2.2 使用protoc生成代码
# python
protoc --python_out=. api.proto
# go
protoc --go_out=plugins=grpc:. api.proto
2.2 protobuf语法
采用Message定义(class),字段格式如下
[rules][data_type] [data_name] = [number] [default = obj]
Rules:定义该变量的规则
- required:必须的
- optional:可选的。如果未设置可选字段值,则使用默认值default
- repeated:可以重复任意次数(包括零次),类似数组。
DataType:数据类型
常用数据类型有int32, int64, float, double, bool, string, bytes
DataName:字段名称
Number:唯一的数值型标识符
Protobuf是采用key-value形式,将成员名映射为number,这样就能实现数据的序列化.每个数据字段都有唯一的数值型标识符.这些标识符用于标识字段在消息中的二进制格式,使用中的类型不应该随意改动。需要注意的是,[1-15]内的标识在编码时只占用一个字节,包含标识符和字段类型。[16-2047]之间的标识符占用2个字节。
例子
syntax = "proto2";
message Person {
required int32 age = 1;
required string name = 2;
}
message Family {
repeated Person person = 1;
}
三、grpc项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2lagTcg3-1640622187959)(C:\Users\v_rwsnowguo\Desktop\图库\pic\2021\12\Snipaste_2021-12-16_10-26-38.png)]
3.1 go.mod
module go-study
go 1.17
require (
google.golang.org/grpc v1.42.0
google.golang.org/protobuf v1.27.1
)
require (
github.com/golang/protobuf v1.5.2 // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
golang.org/x/text v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
)
3.2 proto
shop.proto
syntax = "proto3";// 协议为proto3
package proto;
option go_package = "./";
// 定义发送请求信息
message ProdRequest {
int32 prod_id = 1; // 商品ID
}
// 定义响应信息
message ProdResponse {
optional int32 prod_id = 1;
repeated string prod_name = 2 ; // 商品名称
}
// 定义服务
service ProdService {
rpc GetProd(ProdRequest) returns(ProdResponse);
}
根据proto文件生成go文件
protoc --go_out=plugins=grpc:./proto ./proto/shop.proto
3.3 service
ProdService.go
package service
import (
"context"
"fmt"
)
import . "go-study/proto"
type ProdService struct {
}
func (this *ProdService) GetProd(ctx context.Context, req *ProdRequest) (*ProdResponse, error) {
prodId := req.GetProdId()
fmt.Println("get--", prodId)
prodNames := []string{"奶茶", "可乐"}
return &ProdResponse{ProdId: &prodId, ProdName: prodNames}, nil
}
3.4 server
server.go
package main
import (
db "go-study/proto"
svr "go-study/service"
"google.golang.org/grpc"
"log"
"net"
)
const (
// Address 监听地址
ADDRESS string = ":8081"
// Network 网络通信协议
NETWORK string = "tcp"
)
func main() {
// 监听本地端口
lis, err := net.Listen(NETWORK, ADDRESS)
if err != nil {
log.Fatal("Net.Listen err: %v", err)
}
// 新建gRPC服务器实例
server := grpc.NewServer()
// 注册服务
db.RegisterProdServiceServer(server, new(svr.ProdService))
err = server.Serve(lis)
if err != nil {
return
}
}
3.5 client
client.go
package main
import (
ctx "context"
"fmt"
db "go-study/proto"
"google.golang.org/grpc"
"log"
)
const (
// Address 连接地址
Address string = "127.0.0.1:8081"
)
func main() {
conn, err := grpc.Dial(Address, grpc.WithInsecure())
//grpc.WithInsecure()禁止安全验证传输
if err != nil {
log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()
// 建立gRPC连接
client := db.NewProdServiceClient(conn)
res, err := client.GetProd(ctx.Background(), &db.ProdRequest{ProdId: 12})
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
}