1. gRPC简介
gRPC是一个高效、开源、基于HTTP/2协议标准的RPC框架,主要由Google开发并维护。gRPC支持多种编程语言,包括Golang、Java、C++、Python等,能够实现跨语言通信。在gRPC中,客户端可以像调用本地函数一样调用远程服务函数,gRPC框架会将请求和响应数据进行序列化和反序列化,以保证数据的可靠传输。
下面我们将使用Golang语言来实现一个简单的gRPC应用。
2. 前置条件
在开始使用gRPC之前,需要先下载安装相应的依赖、插件和工具。具体步骤如下:
2.1 安装Protocol Buffers
gRPC使用Protocol Buffers进行数据序列化和反序列化,因此需要安装相应的编译器和运行时环境。可以使用以下命令下载和安装:
sudo apt-get install protobuf-compiler
go get -u github.com/golang/protobuf/protoc-gen-go
2.2 安装gRPC
可以使用以下命令下载和安装gRPC:
go get -u google.golang.org/grpc
3. 编写服务端代码
我们将使用Golang语言来实现一个简单的gRPC服务端应用,该应用接收客户端的请求,然后将请求的参数打印出来,并返回一个简单的字符串。
3.1 编写.proto文件
在使用gRPC之前,需要定义.proto文件来描述请求和响应数据结构、函数签名以及其他元信息。我们可以在.proto文件中指定数据类型、RPC服务等信息,然后使用protoc编译成相应的代码。
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
3.2 创建服务端应用
在完成.proto文件的编写后,我们可以使用protoc命令生成Golang语言的代码。
protoc --go_out=plugins=grpc:. helloworld.proto
生成的代码中包含服务端和客户端的代码,其中包括服务端的代码如下:
type server struct{}
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", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("grpc server started on %s", lis.Addr().String())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
在上述代码中,我们创建了一个server结构,然后实现了SayHello函数,该函数接收一个context和一个HelloRequest对象,然后打印请求参数,返回一个HelloReply对象。
接下来,我们使用grpc.NewServer函数创建一个Server对象,然后调用pb.RegisterGreeterServer函数将服务注册到gRPC服务器上。最后调用s.Serve(lis)启动服务器,监听对应的端口并接受客户端请求。
4. 编写客户端代码
我们将使用Golang语言来实现一个简单的gRPC客户端应用,该应用向服务端发送请求,然后将服务端返回的响应进行处理。
4.1 创建客户端应用
在客户端应用中,我们需要连接到服务器,然后调用定义在.proto文件中的函数,最后使用收到的响应结果进行处理。
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
name := "world"
if 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("failed to say hello: %v", err)
}
log.Printf("Response: %s", r.GetMessage())
在上述代码中,我们使用grpc.Dial函数创建一个客户端连接,然后调用pb.NewGreeterClient函数创建一个GreeterClient对象。接着,我们调用SayHello函数,传入一个HelloRequest对象,并得到HelloReply对象作为响应结果。
5. 使用Golang中的gRPC并发传输数据
在实际开发中,我们可能需要处理大量的数据,并且需要实现高效的并发传输。gRPC提供了流传输的功能,即可以通过流实现数据的并发传输。在gRPC中,流可以分为三种类型:Unary、Server-side streaming和Client-side streaming。具体解释如下:
Unary: 客户端向服务端发送一次请求,服务端返回一次响应。
Server-side streaming: 客户端向服务端发送一次请求,服务端返回多次响应。
Client-side streaming: 客户端向服务端发送多次请求,服务端返回一次响应。
下面我们来分别实现这三种流传输方式。
5.1 Unary流传输
定义.proto文件:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc LotsOfReplies(HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
在上述代码中,我们添加了一个LotsOfReplies函数,该函数接收一个HelloRequest对象,并返回一个包含多个HelloReply对象的stream流。
定义服务端代码:
func (s *server) LotsOfReplies(req *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {
log.Printf("Received: %v", req.GetName())
for i := 0; i < 10; i++ {
message := fmt.Sprintf("Hello %v", i)
r := &pb.HelloReply{Message: message}
if err := stream.Send(r); err != nil {
return err
}
}
return nil
}
在上述代码中,我们实现了一个LotsOfReplies函数,该函数接收一个HelloRequest对象和一个pb.Greeter_LotsOfRepliesServer对象,然后使用for循环发送10个HelloReply对象。
客户端代码:
func callUnary(c pb.GreeterClient) {
name := "world"
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", res.Message)
}
在上述代码中,我们实现了一个callUnary函数,该函数接收一个GreeterClient对象,然后使用context.WithTimeout函数创建一个上下文,最后调用SayHello函数。
5.2 Server-side streaming流传输
服务端代码:
func (s *server) LotsOfReplies(req *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {
log.Printf("Received: %v", req.GetName())
for i := 0; i < 10; i++ {
message := fmt.Sprintf("Hello %v", i)
r := &pb.HelloReply{Message: message}
if err := stream.Send(r); err != nil {
return err
}
time.Sleep(time.Second)
}
return nil
}
在上述代码中,我们对LotsOfReplies函数进行了修改,在发送HelloReply对象之前增加了一个Sleep函数,用于模拟数据处理的时间。
客户端代码:
func callServerStreaming(c pb.GreeterClient) {
name := "world"
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
stream, err := c.LotsOfReplies(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("failed to receive: %v", err)
}
log.Printf("Greeting: %s", res.Message)
}
}
在上述代码中,我们实现了一个callServerStreaming函数,该函数接收一个GreeterClient对象,然后使用context.WithTimeout函数创建一个上下文,最后调用LotsOfReplies函数,并循环接收服务端返回的HelloReply对象。
5.3 Client-side streaming流传输
定义.proto文件:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc LotsOfReplies(HelloRequest) returns (stream HelloReply) {}
rpc LotsOfGreetings(stream HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
在上述代码中,我们添加了一个LotsOfGreetings函数,该函数接收一个HelloRequest流,然后服务端返回一个HelloReply响应。
服务端代码:
func (s *server) LotsOfGreetings(stream pb.Greeter_LotsOfGreetingsServer) error {
var names []string
for {
req, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.HelloReply{Message: "Hello " + strings.Join(names, ",")})
}
if err != nil {
return err
}
names = append(names, req.GetName())
}
}
在上述代码中,我们实现了一个LotsOfGreetings函数,该函数接收一个pb.Greeter_LotsOfGreetingsServer流,然后使用循环接收HelloRequest请求,并将请求参数添加到names列表中,最后返回一个HelloReply响应,包含names列表中的所有参数。
客户端代码:
func callClientStreaming(c pb.GreeterClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
stream, err := c.LotsOfGreetings(ctx)
if err != nil {
log.Fatalf("failed to call lots of greetings: %v", err)
}
names := []string{"Foo", "Bar", "Baz"}
for _, name := range names {
req := &pb.HelloRequest{Name: name}
if err := stream.Send(req); err != nil {
log.Fatalf("failed to send: %v", err)
}
}
res, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("failed to receive: %v", err)
}
log.Printf("Greeting: %s", res.Message)
}
在上述代码中,我们实现了一个callClientStreaming函数,该函数接收一个GreeterClient对象,然后使用context.WithTimeout函数创建一个上下文,最后调用LotsOfGreetings函数,并将HelloRequest对象发送给服务端,然后接收返回的HelloReply响应。
6. 总结
本文我们介绍了gRPC的基本概念和使用,并且通过实际的示例代码演示了gRPC在Golang中的并发传输数据的最佳实践。通过本文的学习,相信读者已经对gRPC的使用和流传输方式有了更深入的了解。