0. 概述

对于有跟踪我博客文章的同学可能或多或少都有点印象在我的博客中已经写过一些关于 GRPC 和 Protobuf 的文章,其中有一篇: ProtoBuf 与 gRPC 是介绍 Protobuf 的,当然里面也有一点关于 GRPC 的内容,但是我觉得写得不咋地,并没有太多借鉴意义;还有另外一篇是:Go 语言中使用 gRPC 构建 C/S 系统【译】,这是一篇翻译的文章,这篇文章很全面,讲述了很多关于 GRPC 的内容,但是因为要讲的东西太多了,所以就难免都是一笔带过。

所以,我准备从最开始一点一点的讲起,因为 GRPC 的实现语言可以是多种语言,实现方式不限,所以,这里我将以我的喜好使用 Go 语言进行编写,当然,如果我想再来一个门语言作为示例的话,那么毫无疑问,它将是 Python。

1. Protobuf

对于 GRPC,一开始我们肯定要从 Protobuf 开始说起,这里可能你会有问题,一定要先有 Protobuf 么?是的,从 GRPC 的官方网站上可以看到下面的这个介绍:

这里短短的一句话就隐含了多个信息:

那既然如此,就不妨从 service 的定义开始说起,来看下 GRPC 的 Service 要如何编写。

2. 定义 service

这里可以看到,service 的定义其实还是比较简单的,有点类似于与 Go 中的 struct 的定义,不同之处在于这里更加精简:

  1. service Greeter {
  2. }

这就定义了一个 service,一个 service 直译就是一个服务的意思,从业务上来说,对于有相关联的一组操作都会放在同一个 service 中,例如登录、注册和登出操作可能都会放到一个叫做 Auth 的 service 里面,例如:

从这里也可以看到,定义操作的语法是:

  1. rpc Operation (RequestBody) returns (ResponseBody) {}

这里的 RequestBodyResponseBody 都是 Protobuf 里面的 message,可以简单得理解为 Go 中的 struct,实际上,当我们写代码的时候也就是 struct。同时,从 Logout 的定义中我们可以看到,响应体是 google.empty.Empty,表示一个空的响应体,当我们不关心响应内容的时候,可以使用这种默认的响应体。

这里还有一点你可能会有点好奇的就是操作的定义为什么要以 {} 结尾?这其实是 GRPC 提供的一种扩展机制,对于一个 operation 如果有其他额外的限制,那么就可以在里面定义,这个后面会遇到。

就这么简单,在 GRPC 的 proto 中需要定义 service,然后定义 service 允许的操作,然后操作的输入和输出是什么就可以了,然后就可以根据这个来写代码的。当然,当然,如果你够强,你可以自己根据一个 proto 的描述来写 Server 和 Client,但是,需要提醒一下的是,如果你准备这么做,那么你不仅需要实现 GRPC 的通信协议,还需要自己进行 Protobuf 的序列化和反序列化,同时定义和 proto 中相同的 GO Struct,应该也不难,可能整个写下来对于强大的你来说也就一星期的事。

3. 编译 proto

但是,Google 很懂得关怀像我这样的凡人,只要我定义好了 proto,那么 Google 就提供了工具直接从 proto 编译出 Go 语言的 Client 和 Server 端的框架,这包含了前面说的 GRPC 的通信协议,Protobuf 和 Go Struct 之间的序列化和反序列化,同时还有 Server 端的 interface,而我需要做的就是 Implement 这个 interface,填充业务代码,然后 start 一下 Server 就可以了。既然有这么好的工具,那么要怎么来安装呢?如果你本地已经安装了 1.6 版本及以上版本的 Go,那么你可以直接使用这条命令安装:

  1. $ go get -u github.com/golang/protobuf/protoc-gen-go

安装完之后,如果你手上没有可以用的 proto 又不想自己写的话,没关系,我为你提供了一份:Github Codes,记得 clone 下来之后要切换到这个目录,然后尝试执行一下一下命令:

  1. $ mkdir go
  2. $ protoc -I protos/ protos/helloworld.proto --go-grpc_out=require_unimplemented_servers=false:./go --go_out=./go
  3. $ vim go/helloworld.pb.go

然后看看里面都有什么内容,你至少会看到这两个内容:

从这里可以看出,如果我们想开箱即用的话,那么 Client 端是不用实现啦,因为已经有了一个默认的实现,但是对于 Server 端还是要自己实现的,想想也应该是这样的,毕竟 Client 端无需实现业务,而 Server 端是业务的提供方,不能由框架帮你实现,所以得自己做。

4. 实现代码

OK,到了这里,是不是可以 Coding 了呢?是的,Coding 也是非常简单,将生成的 Go 文件 helloworld.pb.go 放到我们项目中的合适位置,这个位置不强求,可以根据自己的项目规划合理存放,甚至于都可以放到其他项目,然后由实现的项目 import 进来。我在 Github 上也给了一个我的实现: helloworld.go

5. 运行业务

这样,我们实现好了我们的业务代码,那么要怎么讲这段代码跑起来提供业务服务呢?这里又是一个点了,如果你想自己写一个 Server 来提供服务的话是完全没问题的,但是 Google 还是为想偷懒的人做好了脚手架啦,但是在使用之前你得先安装 GRPC 的 Go 库来继续后续的工作:

  1. $ go get -u google.golang.org/grpc

然后我在 Github 上也提供了我的代码: main.go

这样,你的 GRPC 服务端就算是 Ready 了,等待客户端的访问了。这里或许你会有一点小不舒服的就是为什么生成出来的 package 名字是 helloworld,可以换成其他名字么?或者说我们可以自定义名字么?很显然,这都是可以的,那么在哪里实现呢?让我们回到 proto 的定义,打开 proto 文件,你会发现这么一截:

从这里可以看到,上面 17-20 行定义了 Java 的行为,这个我们不关注,但是 Line 22 确实我们介意的点,尝试一下修改看看。

6. 客户端访问

前面说了,我们的 Server 端已经 Ready 了,是不是可以写个 Client 端来试试看能够调用成功啦?Client 端的编写会比 Server 端简单太多,毕竟因为不用写业务实现嘛,我也给出了我的实现:client.go

这个时候你尝试一下运行客户端,那么你将会看到这样的输出结果:

是的,当你看到这个的时候,表示你成功了,你的第一个 GRPC 应用跑起来了。到此,我也就从 Proto 的定义一步步得介绍完到生成脚手架,然后再通过脚手架开发代码,启动服务,最后自己写一个客户端尝试调用,并且发现服务正常的整个流程。通过这个流程,我相信你已经能够自己去实现你心中那个伟大的项目了,别犹豫,是时候打开编辑器开始你的 GRPC 之旅了。