来源:深圳兄弟连教育
时间:2019/7/26 15:33:50
gRPC简介
gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。本节将讲述gRPC的简单用法。
gRPC框架技术栈
Go语言的gRPC技术栈如图4-1所示:
说明:
1、底层为TCP或Unix Socket协议。
2、第二层是HTTP/2协议的实现。
3、针对Go语言的gRPC核心库。
4、应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信。
gRPC的安装
命令安装通过如下命令可以安装go版本的gRPC框架:
go get -u google.golang.org/grpc
通过go的get命令可以安装完成。
下载安装如果安装失败,也可以自行到github网站下载grpc-go代码库,然后解压,放入到当前系统的$GOPATH/src中,创建google.golang.org目录,将解压后的文件放入到新创建的目录中,修改解压后的目录名称为grpc,
gRPC使用方法
1、准备.proto文件
创建person.proto文件,定义HelloService接口:
syntax = "proto3";package person;message Person { string name = 1;
int32 age = 2;}service HelloService{ rpc Hello (Person) returns
(Person);}
2、使用gRPC插件生成gRPC代码:
$ protoc --go_out=plugins=grpc:. person.proto
gRPC插件会为服务端和客户端生成不同的接口:
type HelloServiceServer interface { Hello(context.Context, *Person)
(*Person, error)}type HelloServiceClient interface { Hello(ctx context.Context,
in *Person, opts ...grpc.CallOption) (*Person, error)}
gRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。
3、基于HelloServiceServe接口实现具体服务
基于服务端的HelloServiceServer接口可以重新实现HelloService服务:
type HelloService struct {}func (hello *HelloService) Hello(ctx
context.Context, args *person.Person) (*person.Person, error) { reply :=
&person.Person{Name: "hello:" + args.GetName()} return reply, nil}
4、gRPC服务启动流程
在服务端的main中,执行启动服务和监听程序,如下所示:
func main() { grpcServer := grpc.NewServer()
person.RegisterHelloServiceServer(grpcServer, new(HelloService)) lis, err :=
net.Listen("tcp", ":1234") if err != nil { log.Fatal(err) }
grpcServer.Serve(lis)}
首先是通过grpc.NewServer()构造一个gRPC服务对象,然后通过gRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过grpcServer.Serve(lis)在一个监听端口上提供gRPC服务。
5、客户端链接gRPC服务
在客户端中使用grpc连接rpc服务的实现也很简单:
func main() { //1、Dail连接 conn, err := grpc.Dial("localhost:1234",
grpc.WithInsecure()) if err != nil { log.Fatal(err) } defer conn.Close() client
:= person.NewHelloServiceClient(conn) per := &person.Person{Name: "Davie",
Age: 18} response, err := client.Hello(context.Background(), per) if err != nil
{ log.Fatal(err) } fmt.Println(response.GetName(), response.GetAge())}
其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。
gRPC和标准的RPC的区别
gRPC和标准库的RPC框架有一个区别,gRPC生成的接口并不支持异步调用。不过我们可以在多个Goroutine之间安全地共享gRPC底层的HTTP/2链接,因此可以通过在另一个Goroutine阻塞调用的方式模拟异步调用。
gRPC流
RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,gRPC框架针对服务器端和客户端分别提供了流特性。
服务端或客户端的单向流是双向流的特例,我们在HelloService增加一个支持双向流的Channel方法:
rpc Channel (stream Person) returns (stream Person);
关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流。
1、 重新编译person.proto文件
通过如下命令重新编译:
protoc --go_out=plugins=grpc:. person.proto
重新生成代码可以看到接口中新增加的Channel方法的定义:
type HelloServiceServer interface { Hello(context.Context, *Person)
(*Person, error) Channel(HelloService_ChannelServer) error}type
HelloServiceClient interface { Hello(ctx context.Context, in *Person, opts
...grpc.CallOption) (*Person, error) Channel(ctx context.Context, opts
...grpc.CallOption) (HelloService_ChannelClient, error)}
2、生成新的方法参数
在服务端的Channel方法参数是一个新的HelloService_ChannelServer类型的参数,可以用于和客户端双向通信。客户端的Channel方法返回一个HelloService_ChannelClient类型的返回值,可以用于和服务端进行双向通信。
HelloService_ChannelServer和HelloService_ChannelClient均为接口类型:
type HelloService_ChannelServer interface { Send(*Person) error Recv()
(*Person, error) grpc.ServerStream}type HelloService_ChannelClient interface {
Send(*Person) error Recv() (*Person, error) grpc.ClientStream}
3、服务端实现流接口方法实现
通过以上代码,我们可以发现服务端和客户端的流辅助接口均定义了Send和Recv方法用于流数据的双向通信。
func (hello *HelloService) Channel(stream
person.HelloService_ChannelServer) error { for { args, err := stream.Recv() if
err != nil { if err == io.EOF { return nil } return err } response :=
&person.Person{Name: "姓名:" + args.GetName(), Age: args.GetAge()} err =
stream.Send(response) if err != nil { return nil } }}
服务端在循环中接收客户端发来的数据,如果遇到io.EOF表示客户端流被关闭,如果函数退出表示服务端流关闭。生成返回的数据通过流发送给客户端,双向流数据的发送和接收都是完全独立的行为。需要注意的是,发送和接收的操作并不需要一一对应,用户可以根据真实场景进行组织代码。
4、客户端实现流方法的调用
客户端需要先调用Channel方法获取返回的流对象:
stream, err := client.Channel(context.Background()) if err != nil {
log.Fatal(err) }
将发送和接收操作放到两个独立的Goroutine。向服务端发送数据和接受数据代码实现如下:
//向服务端发送数据 go func() { for { if err := stream.Send(&person.Person{Name:
"Davie", Age: 18}); err != nil { log.Fatal(err) } time.Sleep(time.Second) } }()
//循环中接收服务端返回的数据 for { response, err := stream.Recv() if err != nil { if err ==
io.EOF { break } log.Fatal(err) } fmt.Println(response.GetName())
通过该案例和代码,我们演示了gRPC框架的使用流程,以及gRPC框架流的使用。
版权所有:搜学搜课(www.soxsok.com)