目录
一、介绍
二、环境准备
三、Golang中使用grpc
1.编写protobuf文件
2.服务端
3.客户端
四、proto文件详解
1.proto语法
2.数据类型
基本数据类型
数组类型
map类型
嵌套类型
编写风格
3.多服务
4.多个proto文件
五、流式传输
1.普通rpc
2.服务器流式
3.客户端流式
4.双向流
六、配套代码
一、介绍
RPC
是Remote Procedure Call
的简称,中文叫远程过程调用,简单的说,就是调用远程方法和调用本地方法一样
那么grpc就是由 google
开发的一个高性能、通用的开源RPC
框架
官网地址:Introduction to gRPC | gRPC
- gRPC是一种现代开源高性能远程过程调用(RPC)框架,可在任何环境中运行。
- 它可以高效地连接数据中心内的服务,并支持负载平衡、跟踪、健康检查和身份验证等插件功能。
- 它适用于分布式计算的最后一英里,以连接设备、移动应用程序和浏览器到后端服务。
- 公司已使用gRPC连接其环境中的多个服务,从连接少数服务到跨多种语言的数据中心内数百种服务。
- gRPC最初由谷歌创建,用于连接在其数据中心内和跨越其数据中心的大量微服务。
二、环境准备
以Golang使用为例,只需要在windows上安装protoc转换工具
官网地址:Go | gRPC
# 下载网址:
https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protoc-3.9.0-win64.zip
# go语言需要安装的依赖
go get github.com/golang/protobuf/proto
go get google.golang.org/grpc
go install github.com/golang/protobuf/protoc-gen-go
安装好之后,需要将protoc的bin目录添加到环境变量中
还需要将protoc-gen-go.exe的目录添加到环境变量中
刚刚添加之后,可能需要重启电脑或者重启goland,才能在goland的terminal中使用
三、Golang中使用grpc
整体结构如图:
1.编写protobuf文件
现在还没有学过怎么编写,不用担心,先复制粘贴就行了,主要是用于测试环境是否正常
创建文件夹 /grpc_proto 在该文件夹中创建文件 hello.proto ,编写内容如下:
syntax = "proto3"; // 指定proto版本
package hello_grpc; // 指定默认包名// 指定golang包名
option go_package = "/hello_grpc";//定义rpc服务
service HelloService {// 定义函数rpc SayHello (HelloRequest) returns (HelloResponse) {}
}// HelloRequest 请求内容
message HelloRequest {string name = 1;string message = 2;
}// HelloResponse 响应内容
message HelloResponse{string name = 1;string message = 2;
}
在文件夹 /grpc_proto 中执行命令:protoc -I . --go_out=plugins=grpc:. .\hello.proto
或者编写set.bat批处理文件,方便使用,内容如下:
protoc -I . --go_out=plugins=grpc:.\grpc_proto .\grpc_proto\hello.proto
2.服务端
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/grpclog""net""oslee/grpc_study/1base/grpc_proto/hello_grpc"
)// HelloServer 得有一个结构体,需要实现这个服务的全部方法,叫什么名字不重要
type HelloServer struct {
}func (HelloServer) SayHello(ctx context.Context, request *hello_grpc.HelloRequest) (*hello_grpc.HelloResponse, error) {fmt.Println("入参:", request.Name, request.Message)return &hello_grpc.HelloResponse{Name: "server",Message: "hello " + request.Name,}, nil
}func main() {// 监听端口listen, err := net.Listen("tcp", ":8080")if err != nil {grpclog.Fatalf("Failed to listen: %v", err)}// 创建一个gRPC服务器实例。s := grpc.NewServer()server := HelloServer{}// 将server结构体注册为gRPC服务。hello_grpc.RegisterHelloServiceServer(s, &server)fmt.Println("grpc server running :8080")// 开始处理客户端请求。err = s.Serve(listen)
}
3.客户端
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""log""oslee/grpc_study/1base/grpc_proto/hello_grpc"
)func main() {addr := ":8080"// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。// 此处使用不安全的证书来实现 SSL/TLS 连接conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))}defer conn.Close()// 初始化客户端client := hello_grpc.NewHelloServiceClient(conn)result, err := client.SayHello(context.Background(), &hello_grpc.HelloRequest{Name: "client",Message: "hello",})fmt.Println(result, err)
}
调用结果如下:
四、proto文件详解
1.proto语法
- service 对应的就是go里面的接口,可以作为服务端,客户端
- rpc 对应的就是结构体中的方法
- message对应的也是结构体
2.数据类型
基本数据类型
message Request {
double a1 = 1;
float a2 = 2;
int32 a3 = 3;
uint32 a4 = 4;
uint64 a5 = 5;
sint32 a6 = 6;
sint64 a7 = 7;
fixed32 a8 = 8;
fixed64 a9 = 9;
sfixed32 a10 = 10;
sfixed64 a11 = 11;
bool a12 = 12;
string a13 = 13;
bytes a14 = 14;
}
对应go类型
type Request struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFieldsA1 float64 `protobuf:"fixed64,1,opt,name=a1,proto3" json:"a1,omitempty"`
A2 float32 `protobuf:"fixed32,2,opt,name=a2,proto3" json:"a2,omitempty"`
A3 int32 `protobuf:"varint,3,opt,name=a3,proto3" json:"a3,omitempty"`
A4 uint32 `protobuf:"varint,4,opt,name=a4,proto3" json:"a4,omitempty"`
A5 uint64 `protobuf:"varint,5,opt,name=a5,proto3" json:"a5,omitempty"`
A6 int32 `protobuf:"zigzag32,6,opt,name=a6,proto3" json:"a6,omitempty"`
A7 int64 `protobuf:"zigzag64,7,opt,name=a7,proto3" json:"a7,omitempty"`
A8 uint32 `protobuf:"fixed32,8,opt,name=a8,proto3" json:"a8,omitempty"`
A9 uint64 `protobuf:"fixed64,9,opt,name=a9,proto3" json:"a9,omitempty"`
A10 int32 `protobuf:"fixed32,10,opt,name=a10,proto3" json:"a10,omitempty"`
A11 int64 `protobuf:"fixed64,11,opt,name=a11,proto3" json:"a11,omitempty"`
A12 bool `protobuf:"varint,12,opt,name=a12,proto3" json:"a12,omitempty"`
A13 string `protobuf:"bytes,13,opt,name=a13,proto3" json:"a13,omitempty"`
A14 []byte `protobuf:"bytes,14,opt,name=a14,proto3" json:"a14,omitempty"`
}
标量类型
.proto Type | 解释 | Go Type |
---|---|---|
double | float64 | |
float | float32 | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 |
uint32 | 使用变长编码 | uint32 |
uint64 | 使用变长编码 | uint64 |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效 | int64 |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 |
sfixed32 | 总是4个字节 | int32 |
sfixed64 | 总是8个字节 | int64 |
bool | bool | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本 | string |
bytes | 可能包含任意顺序的字节数据 | []byte |
标量类型如果没有被赋值,则不会被序列化,解析时,会赋予默认值
- strings:空字符串
- bytes:空序列
- bools:false
- 数值类型:0
数组类型
message ArrayRequest {
repeated int64 a1 = 1;
repeated string a2 = 2;
repeated Request request_list = 3;
}
对应go类型
type ArrayRequest struct {
A1 []int64
A2 []string
RequestList []*Request
}
map类型
键只能是基本类型
message MapRequest {
map<int64, string> m_i_s = 1;
map<string, bool> m_i_b = 2;
map<string, ArrayRequest> m_i_arr = 3;
}
对应go类型
type MapRequest struct {
MIS map[int64]string
MIB map[string]bool
MIArr map[string]*ArrayRequest
}
嵌套类型
message Q1 {
message Q2{
string name2 = 2;
}
string name1 = 1;
Q2 q2 = 2;
}
对应go类型
type Q1 struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name1 string `protobuf:"bytes,1,opt,name=name1,proto3" json:"name1,omitempty"`
Q2 *Q1_Q2 `protobuf:"bytes,2,opt,name=q2,proto3" json:"q2,omitempty"`
}
个人习惯不嵌套,分开写
编写风格
- 文件名建议下划线,例如:my_student.proto
- 包名和目录名对应
- 服务名、方法名、消息名均为大驼峰
- 字段名为下划线
3.多服务
proto文件
syntax = "proto3"; // 指定proto版本 // 指定golang包名 option go_package = "/duo_server";service VideoService {rpc Look(Request)returns(Response){} }service OrderService {rpc Buy(Request)returns(Response){} }message Request{string name = 1; } message Response{string name = 1; }
服务端
package mainimport ("context""fmt""google.golang.org/grpc""log""net""oslee/grpc_study/2duo_server/grpc_proto/duo_server"
)type VideoServer struct {
}func (VideoServer) Look(ctx context.Context, request *duo_server.Request) (res *duo_server.Response, err error) {fmt.Println("video:", request)return &duo_server.Response{Name: "server",}, nil
}type OrderServer struct {
}func (OrderServer) Buy(ctx context.Context, request *duo_server.Request) (res *duo_server.Response, err error) {fmt.Println("order:", request)return &duo_server.Response{Name: "server",}, nil
}func main() {listen, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal(err)}s := grpc.NewServer()duo_server.RegisterVideoServiceServer(s, &VideoServer{})duo_server.RegisterOrderServiceServer(s, &OrderServer{})fmt.Println("grpc server程序运行在:8080")err = s.Serve(listen)
}
客户端
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""log""oslee/grpc_study/2duo_server/grpc_proto/duo_server"
)func main() {addr := ":8080"// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。// 此处使用不安全的证书来实现 SSL/TLS 连接conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))}defer conn.Close()orderClient := duo_server.NewOrderServiceClient(conn)res, err := orderClient.Buy(context.Background(), &duo_server.Request{Name: "client",})fmt.Println(res, err)videoClient := duo_server.NewVideoServiceClient(conn)res, err = videoClient.Look(context.Background(), &duo_server.Request{Name: "client",})fmt.Println(res, err)}
4.多个proto文件
当项目大起来之后,会有很多个service,rpc,message
我们会将不同服务放在不同的proto文件中,还可以放一些公共的proto文件
本质就是生成go文件,需要在一个包内
proto文件
common.proto
syntax = "proto3"; package proto; option go_package = "/proto";message Request{string name = 1; } message Response{string name = 1; }order.proto
syntax = "proto3"; package proto; option go_package = "/proto"; import "common.proto";service OrderService {rpc Look(Request)returns(Response){} }video.proto
syntax = "proto3"; package proto; option go_package = "/proto"; import "common.proto";service VideoService {rpc Look(Request)returns(Response){} }
服务端
package mainimport ("context""fmt""google.golang.org/grpc""log""net""oslee/grpc_study/3duo_proto/grpc_proto/proto"
)type VideoServer struct {
}func (VideoServer) Look(ctx context.Context, request *proto.Request) (res *proto.Response, err error) {fmt.Println("video:", request)return &proto.Response{Name: "server",}, nil
}type OrderServer struct {
}func (OrderServer) Look(ctx context.Context, request *proto.Request) (res *proto.Response, err error) {fmt.Println("order:", request)return &proto.Response{Name: "server",}, nil
}func main() {listen, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal(err)}s := grpc.NewServer()proto.RegisterVideoServiceServer(s, &VideoServer{})proto.RegisterOrderServiceServer(s, &OrderServer{})fmt.Println("grpc server程序运行在:8080")err = s.Serve(listen)
}
客户端
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""log""oslee/grpc_study/3duo_proto/grpc_proto/proto"
)func main() {addr := ":8080"// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。// 此处使用不安全的证书来实现 SSL/TLS 连接conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))}defer conn.Close()orderClient := proto.NewOrderServiceClient(conn)res, err := orderClient.Look(context.Background(), &proto.Request{Name: "client",})fmt.Println(res, err)videoClient := proto.NewVideoServiceClient(conn)res, err = videoClient.Look(context.Background(), &proto.Request{Name: "client",})fmt.Println(res, err)
}
五、流式传输
1.普通rpc
一问一答式,文章前面已讲
2.服务器流式
建立连接后,不知道服务端什么时候发送完毕,使用场景:下载文件
proto文件
syntax = "proto3";
option go_package = "/proto";message Request {string name = 1;
}message FileResponse{string file_name = 1;bytes content = 2;
}
service ServiceStream{rpc DownLoadFile(Request)returns(stream FileResponse){}
}
服务端
package mainimport ("fmt""google.golang.org/grpc""io""log""net""os""oslee/grpc_study/4download/grpc_proto/proto"
)type ServiceStream struct{}func (ServiceStream) DownLoadFile(request *proto.Request, stream proto.ServiceStream_DownLoadFileServer) error {fmt.Println(request)file, err := os.Open("F:\\yckj\\workspace_gitee\\1own\\os_lee\\go_grpc_study\\res\\protoc-3.9.0-win64.zip")if err != nil {return err}defer file.Close()for {buf := make([]byte, 2048)_, err = file.Read(buf)if err == io.EOF {break}if err != nil {break}stream.Send(&proto.FileResponse{Content: buf,})}return nil
}func main() {listen, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal(err)}server := grpc.NewServer()proto.RegisterServiceStreamServer(server, &ServiceStream{})server.Serve(listen)
}
客户端
package mainimport ("bufio""context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""io""log""os""oslee/grpc_study/4download/grpc_proto/proto"
)func main() {addr := ":8080"// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。// 此处使用不安全的证书来实现 SSL/TLS 连接conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))}defer conn.Close()// 初始化客户端client := proto.NewServiceStreamClient(conn)stream, err := client.DownLoadFile(context.Background(), &proto.Request{Name: "张三",})file, err := os.OpenFile("F:\\yckj\\workspace_gitee\\1own\\os_lee\\go_grpc_study\\res\\protoc-3.9.0-win64_down.zip", os.O_CREATE|os.O_WRONLY, 0600)if err != nil {log.Fatalln(err)}defer file.Close()writer := bufio.NewWriter(file)var index intfor {index++response, err := stream.Recv()if err == io.EOF {break}fmt.Printf("第%d 次, 写入 %d 数据\n", index, len(response.Content))writer.Write(response.Content)}writer.Flush()
}
3.客户端流式
建立连接后,不知道客户端什么时候发送完毕,使用场景:上传文件
proto文件
syntax = "proto3";
option go_package = "/proto";
message Response {string Text = 1;
}
message FileRequest{string file_name = 1;bytes content = 2;
}
service ClientStream{rpc UploadFile(stream FileRequest)returns(Response){}
}
服务端
package mainimport ("bufio""fmt""google.golang.org/grpc""io""log""net""os""oslee/grpc_study/5upload/grpc_proto/proto"
)type ClientStream struct{}func (ClientStream) UploadFile(stream proto.ClientStream_UploadFileServer) error {file, err := os.OpenFile("F:\\yckj\\workspace_gitee\\1own\\os_lee\\go_grpc_study\\res\\protoc-3.9.0-win64_up.zip", os.O_CREATE|os.O_WRONLY, 0600)if err != nil {log.Fatalln(err)}defer file.Close()writer := bufio.NewWriter(file)var index intfor {index++response, err := stream.Recv()if err == io.EOF {break}writer.Write(response.Content)fmt.Printf("第%d次", index)}writer.Flush()stream.SendAndClose(&proto.Response{Text: "完毕了"})return nil
}func main() {listen, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal(err)}server := grpc.NewServer()proto.RegisterClientStreamServer(server, &ClientStream{})server.Serve(listen)
}
客户端
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""io""log""os""oslee/grpc_study/5upload/grpc_proto/proto"
)func main() {addr := ":8080"// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。// 此处使用不安全的证书来实现 SSL/TLS 连接conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))}defer conn.Close()// 初始化客户端client := proto.NewClientStreamClient(conn)stream, err := client.UploadFile(context.Background())file, err := os.Open("F:\\yckj\\workspace_gitee\\1own\\os_lee\\go_grpc_study\\res\\protoc-3.9.0-win64.zip")if err != nil {log.Fatalln(err)}defer file.Close()for {buf := make([]byte, 2048)_, err = file.Read(buf)if err == io.EOF {break}if err != nil {break}stream.Send(&proto.FileRequest{FileName: "x.png",Content: buf,})}response, err := stream.CloseAndRecv()fmt.Println(response, err)
}
4.双向流
使用场景:聊天室
proto文件
syntax = "proto3";
option go_package = "/proto";message Request {string name = 1;
}
message Response {string Text = 1;
}service BothStream{rpc Chat(stream Request)returns(stream Response){}
}
服务端
package mainimport ("fmt""google.golang.org/grpc""log""net""oslee/grpc_study/6chat/grpc_proto/proto"
)type BothStream struct{}func (BothStream) Chat(stream proto.BothStream_ChatServer) error {for i := 0; i < 10; i++ {request, _ := stream.Recv()fmt.Println(request)stream.Send(&proto.Response{Text: "你好",})}return nil
}func main() {listen, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal(err)}server := grpc.NewServer()proto.RegisterBothStreamServer(server, &BothStream{})server.Serve(listen)
}
客户端
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""log""oslee/grpc_study/6chat/grpc_proto/proto"
)func main() {addr := ":8080"// 使用 grpc.Dial 创建一个到指定地址的 gRPC 连接。// 此处使用不安全的证书来实现 SSL/TLS 连接conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf(fmt.Sprintf("grpc connect addr [%s] 连接失败 %s", addr, err))}defer conn.Close()// 初始化客户端client := proto.NewBothStreamClient(conn)stream, err := client.Chat(context.Background())for i := 0; i < 10; i++ {stream.Send(&proto.Request{Name: fmt.Sprintf("第%d次", i),})response, err := stream.Recv()fmt.Println(response, err)}
}
六、配套代码
代码下载地址:
go_grpc_study: Golang使用grpc教程