4、基于Golang的gRPC入门

接下来我们就开始以go为基础去使用gRPC。本文将介绍:

  • .proto 文件中进行服务定义。

  • 使用 protocol buffer 编译器生成 server 端 和client 端代码。

  • 使用 Go gRPC API 为我们定义的服务编写简单的 client和server。

Example Code

我们这里使用的示例是 grpc/grpc-go/examples/route_guide, 为了使用这个示例,可以先下载 grpc-go 这个库。

然后切换到 grpc/grpc-go/examples/route_guide目录下。

定义服务

从前面的介绍中我们已经介绍如何使用 protocol buffers来定义服务以及返回值类型。

同时在 .proto 文件中还定义了用于请求和响应的message 类型。

生成server端和client 端代码

route_guide目录执行以下的命令。将会在该目录下 生成一个route_guide.pb.go 的go 文件。

这里面将包含:

  • 所有的 protocol buffers代码用于填充,序列化和检索我们的请求和响应消息类型。

  • 客户端使用RouteGuide服务中定义的方法时,调用的接口类型。

  • 服务端要实现的接口类型,以及RouteGuide服务中定义的方法

编写Server端

gRPC已经为我们自动生成了编写Server端需要实现的接口和方法。接下来我们就来实现一个gRPC Server。一共有两个过程:

  • 实现服务定义中的接口

  • 运行gRPC服务,以侦听来自客户端的请求并将其分派给正确的服务实现。

具体示例可以在 grpc-go/examples/route_guide/server/server.go中查看。接下来我们分析下,具体都干了些什么?

实现接口

可以看到有一个 routeGuideServer的结构体,实现了 RouteGuideServer 接口

routeGuideServer 实现了所有的服务接口.

Simple RPC

我们先以最简单的(Simple RPC)为例,来看看他是具体是怎么实现的。他是从客户端获取一个point,并返回一个Feature。

该方法传递了 RPC 上下文对象和客户端的 Point protocol buffers 请求。它返回一个Feature协议缓冲区对象,其中包含响应信息和 error。我们使用适当的信息填充Feature,然后将其与nil错误一起返回,告诉gRPC我们已经完成了RPC的处理,并且Feature可以返回给客户端。

Server-side streaming RPC

接下来看下服务端流式RPC。ListFeatures是服务器端流式RPC,因为我们需要发送多个 Features 给客户端。

如你所见,这次与简单请求的request和reponse不同,这里获取了两个比较复杂的参数pb.Rectangle, RouteGuide_ListFeaturesServer来进行处理并构建response。在这个方法里,我们构建了多个 Feature 对象,并使用send方法将他们写入到RouteGuide_ListFeaturesServer中。返回了一个nil的错误。gRPC层会将其转换为适当的RPC状态,以便在链路中发送。

Client-side streaming RPC

接下来来看一个比较复杂的。客户端流式gRPC.服务端从客户端获取一系列的Points返回单个RouteSummary.

正如我们所看到的,这次没有接收任何的request 请求参数,而是直接接收了一个 RouteGuide_RecordRouteServer流。服务端通过这个流读写消息。读的话,使用Recv()方法,返回单个response 使用它的 SendAndClose() 方法。

在这个方法体中,我们使用 RouteGuide_RecordRouteServerRecv() 方法,重复读取客户端的请求到某一个实体中(本例中是point),直到读取不出任何消息为止。 服务端需要去检查每一次Read返回的错误。如果error为nil,说明当前流没有问题,可以继续读取。如果是io.EOF,说明当前读取已经结束,server端可以返回 RouteSummary,如果它有其他类型的值,则将其原样返回。RPC会自动将其转换成相应的RPC 状态。

Bidirectional streaming RPC

废话不多说,上来先看下代码

这次我们又接收到了RouteGuide_RouteChatServer流,与客户端流式RPC一样,仍然可以使用它来读写数据。但是,这次我们可以在客户端还在向stream中写数据的同时就返回值。

从代码中可以看出,server端这里读写数据的方法非常类似于客户端流式RPC里面的方法,无非就是这里使用了 send()方法,而不是SendAndClose()方法。因为send可以发送多个reponse。尽管每一端都能够按照对方发送消息的顺序读取其相应的消息,但是客户端和服务端都可以按照任何顺序进行读写,这两个流完全独立运行。

启动server端

一旦实现了所有的方法,我们就需要开启一个gRPC server,让客户端能够访问我们的服务。

  • 指定端口

  • 启动 gRPC server实例

  • 将我们实现的service注册到 the gRPC server.

  • Serve()方法启动,Stop()方法停止

编写client端

接下来就开始编写一个 gRPC client 来访问 RouteGuide 服务。 示例 代码 grpc-go/examples/route_guide/client/client.go

创建一个存根/客户端

为了调用服务端发方法,需要创建一个 gRPC channel 来跟server端进行通信。使用 grpc.Dial()方法,传入server端的地址和端口来实现。

还可以使用 DialOptionsgrpc.Dial 设置使用证书认证等,例如下面这样

gRPC channel 建立好之后,需要一个client stub来 执行RPC调用。我们使用 .proto 文件中生成的 pb 包中的 NewRouteGuideClient 方法来创建。

调用服务端方法

接下来我们看客户端如何调用服务端方法。请注意,在gRPC-Go中,RPC以阻塞/同步模式运行,这意味着RPC调用等待服务器响应,并将返回响应或错误。

Simple RPC

就像调用本地方法一样简单,在方法中我们构造了一个 protocol buffers 对象 db.Point,我们还传递了一个context.Context对象,它允许我们在必要时更改RPC的行为,例如rpc调用还在进行时取消RPC. 如果没有出现错误,我们就能收到 server端的返回值。

看一下完整的写法。

Server-side streaming RPC

接下来调用服务端流式RPC方法 ListFeatures,这个方法返回一系列的地理数据 Features.

与简单的RPC调用一样,传入了context.Context来进行request,但是与其不一样的是,这次没有简单收到了一个repsonse对象,而是接收到了一个RouteGuide_ListFeaturesClient实例。客户端会使用 RouteGuide_ListFeaturesClient 来读取server端的消息。

我们使用 RouteGuide_ListFeaturesClientRecv()方法重复读取服务端对 protocol buffers对象(在本例中为Feature)的响应,直到没有更多消息为止。客户端需要对每一个调用中返回的error进行判断,错误的处理方式与server端类似。

看下完整的写法。

Client-side streaming RPC

客户端流方法RPC 方法 RecordRoute类似于服务器端方法,除了我们只传递方法一个上下文并获取一个可以读写消息的RouteGuide_RecordRouteClient流。

RouteGuide_RecordRouteClient 流有一个Send()方法,我们可以用它向server端发送request请求。一旦我们使用 Send()方法发送完了请求,我们就需要调用 CloseAndRecv()关闭这个流,以便gRPC知道我们已经完成了写操作并希望收到response。CloseAndRecv() 方法会返回RPC结果的状态。如果err为nil,那么第一个结果 reply 就是这次请求正确的结果返回值。

Bidirectional streaming RPC

最后,我们来看下双向流式RPC调用 RouteChat()。 与RecordRoute的情况一样,我们只传递方法一个上下文对象并返回一个我们可用于写入和读取消息的流。 但是,我们能够我们能够在向这个流写入数据的同时就通过这个流获取到一些返回值。

下面看下完整的代码实现

读写消息的方式 与 客户端流式RPC 调用很类似,无非就是需要使用 CloseSend()方法来关闭流。尽管客户端和服务端都会按照对方写入的消息顺序来读取,但是他们可以以任意的顺序写入,这个流是完全独立运行的。

Run it

接下来运行以下 server端和client 端试一下。

Last updated

Was this helpful?