【gRPC】gRPC简单使用 protocol
项目使用了RPC,用的是Google的rpc grpc.
简单研究一下是什么
啥是rpc
远程过程调用,简单说就是一台主机需要调用另外一台主机的函数/方法
那么需要解决的问题就是:
- 服务器编写这个被调用方法
- 服务器开放端口进行通信
- 客户端调用
看似只有这简简单单的三个过程,但感觉还是有很多东西的。
其中一个大点,虽然调包程序员不太关心的。但咱是优秀程序员需要着重关注的就是通信。
如何解决调用数据传输的问题?
protocol buffers
protocol buffers 就是grpc用来通信的一个准则。个人也是认为这个rpc的一个精髓
他也是Google开发的一个通信格式,虽然在我浅显看了一眼感觉好像也不算格式,但对于使用者可以暂且这么理解。
他的好处就是他通过二进制文件保存传输,序列化反序列化与传输速度都会很快。缺点就是文件不好看,编写的文档也比json复杂。
那么他是怎么通过二进制传播呢?
(以下内容是我浅显看的,不知道准不准,如果有误,大佬指正)
具体案例
首先先看一下这个rpc具体调用案例。
- 装一下包
- 编写一个.proto文件,文件内部写上你需要通信的rpc函数,以及相关的传输信息
- 翻译,将.proto文件翻译成你这个项目的具体文件,e.g.我用go开发,就翻译成xxx.pb.go
- 编写server.go
- 编写client
- 然后run一下就可以了
// 说明这个proto的语法版本
syntax = "proto3";
// 生成.go的package是什么
option go_package = "github.com/marmotedu/gopractise-demo/apistyle/greeter/helloworld";
// 这个protocol文件自己的package
package helloworld;// The greeting service definition.
service Greeter {// Sends a greetingrpc SayHello (HelloRequest) returns (HelloReply) {}
}// The request message containing the user's name.
message HelloRequest {string name = 1;
}// The response message containing the greetings
message HelloReply {string message = 1;
}
server.go
// Package main implements a server for Greeter service.
package mainimport ("context""log""net"pb "github.com/marmotedu/gopractise-demo/apistyle/greeter/helloworld""google.golang.org/grpc"
)const (port = ":50051"
)// server is used to implement helloworld.GreeterServer.
type server struct {pb.UnimplementedGreeterServer
}// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {log.Printf("Received: %v", in.GetName())return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}func main() {lis, err := net.Listen("tcp", port)if err != nil {log.Fatalf("failed to listen: %v", err)}s := grpc.NewServer()pb.RegisterGreeterServer(s, &server{})if err := s.Serve(lis); err != nil {log.Fatalf("failed to serve: %v", err)}
}
简单概括一点,server编写的过程就是:
- protocol文件已经表明了通信的接口函数是什么样,在server里面编写一个结构体,实现这个接口
- 去listen一个端口
- grpc.New Server
- 将这个new出来的跟你的结构体进行绑定
- 然后再将这个new出来的跟刚才的监听端口绑定就可以了
server准备完毕,就该轮到client了
// Package main implements a client for Greeter service.
package mainimport ("context""log""os""time"pb "github.com/marmotedu/gopractise-demo/apistyle/greeter/helloworld""google.golang.org/grpc"
)const (address = "localhost:50051"defaultName = "world"
)func main() {// Set up a connection to the server.conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()c := pb.NewGreeterClient(conn)// Contact the server and print out its response.name := defaultNameif len(os.Args) > 1 {name = os.Args[1]}ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})if err != nil {log.Fatalf("could not greet: %v", err)}log.Printf("Greeting: %s", r.Message)
}
client.go主要编写方法:
- dail一下,表示自己需要访问哪一个主机的那个端口,构建连接
- 根据构建的连接,调用protocol生成的类方法,绑定刚才的连接
- 通过ctx表明自己这一次rpc的具体是哪一种通信准则(这里不展开,四种通信方式
- 调用方法即可
再回到这个protocol
刚才的简单案例其中很多次都需要跟这个protocol进行交互,其中最关键的是无论是客户端还是服务端他都需要将自己的某个东西与这个protocol绑定在一起
因为,客户端提供服务,编写的方法在自己的文件中,而传输的工作需要交给protocol。所以在server.go需要编写一个结构体,并且重写这个方法,然后进行绑定。这时候protocol才知道具体的实现方法是什么,我应该怎么进行传输。
其次可以注意到,最后在客户端服务端,调用protocol都是像调用一个库的。
这是因为本质上它是将一个.proto文件翻译成一个具体的编程文件,然后内部集成了相关的传输方法,**而这里的传输方法也很容易就能够感知到,其实应该就是本身语言支持的一些byte传输。**所以才说它是一种二进制的传输,这也是为什么他会比较复杂,但是比较快
而在编写文件的时候,就已经说明了这个rpc调用提供的函数是什么样的,那么他就知道传入什么,传出什么。
最后一个细节点,因为无论是服务端还是客户端都需要编写这个protocol文件,但是他才不知道你是客户端还是服务端,所以生成的代码里面是有客户端的具体类也有服务端的具体类。所以才能够看见刚才的样例是两个公用一个protocol代码。