前置条件:

获取 gRPC-go 源码

$ go get google.golang.org/grpc

简单例子的源码位置:

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

复杂些例子的源码位置:

$ cd $GOPATH/src/google.golang.org/grpc/examples/route_guide

写一个gRPC的服务,一般分下面几步:

  • 在一个 .proto 文件内定义服务。
  • 用 protocol buffer 编译器生成服务器和客户端代码。
  • 使用 gRPC 的 Go API 为你的服务实现一个简单的客户端和服务器。

定义服务

在一个 .proto 文件中定义服务

简单的例子服务定义在: examples/helloworld/helloworld/helloworld.proto

route_guide 的定义在 : examples/route_guide/routeguide/route_guide.proto

要定义一个服务,你必须在你的 .proto 文件中指定 service:

然后在你的服务中定义 rpc 方法,指定请求的和响应类型。

gRPC 允许你定义4种类型的 service 方法,这些都在 RouteGuide 服务中使用到了:

简单RPC

一个 简单 RPC , 客户端发送带参请求到服务器并等待响应返回,就像平常的函数调用一样。

服务器端流式 RPC

一个 服务器端流式 RPC , 客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。从例子中可以看出,通过在 响应 类型前插入 stream 关键字,可以指定一个服务器端的流方法。

客户端流式 RPC

一个 客户端流式 RPC , 客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。通过在 请求 类型前指定 stream 关键字来指定一个客户端的流方法。

双向流式 RPC

一个 双向流式 RPC 是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。 每个流中的消息顺序被预留。你可以通过在请求和响应前加 stream 关键字去制定方法的类型。

消息类型

上面看起来像函数参数、返回值得,由于要涉及到跨服务器调用,这些其实传递的是消息。我们的 .proto 文件也包含了所有请求的 protocol buffer 消息类型定义以及在服务方法中使用的响应类型——比如,下面的Point消息类型:

 

生成服务器和客户端代码

我们需要通过 protocol buffer 的编译器 protoc 以及一个特殊的 gRPC Go 插件来完成用 protocol buffer 编译器生成服务器和客户端代码。

 

简单期间,有个 bash 脚本可以帮我们生成合适的代码  codegen.sh  (https://github.com/grpc/grpc-go/blob/master/codegen.sh)

 

运行 codegen.sh route_guide.proto 就可以在当前目录下产生 route_guide.pb.go 文件。

在这个产生的文件中, 既有 routeGuideClient 客户端代码部分, 也有 routeGuideRouteChatServer 服务器段代码部分。

创建服务器

这部分的源码在: grpc-go/examples/route_guide/server/server.go

让 RouteGuide 服务工作有两个部分:

  • 实现我们服务定义的生成的服务接口:做我们的服务的实际的“工作”。
  • 运行一个 gRPC 服务器,监听来自客户端的请求并返回服务的响应。

 

实现RouteGuide

在源码中,我们可以看到实现了接口RouteGuideServer的routeGuideServer数据结构。  这个接口是在route_guide.pb.go中自动产生的。

简单RPC

GetFeature,它从客户端拿到一个 Point 对象,然后从返回包含从数据库拿到的feature信息的 Feature.

该方法传入了 RPC 的上下文对象,以及客户端的 Point 参数。它返回了Feature 响应信息和error信息。

在方法中我们遍历所有服务器端保存的信息,找到位置信息匹配的,然后将其和一个nil错误一起返回给客户端。

 

服务器端流式 RPC

ListFeatures 是一个服务器端的流式 RPC,我们将多个 Feature 发回给客户端。

这里的请求参数是一个 Rectangle,客户端期望返回多个 Feature,这次我们使用了一个请求对象和一个特殊的RouteGuide_ListFeaturesServer来写入我们的响应,而不是得到方法参数中的入参和返回值。

在这个方法中,我们填充了尽可能多的 Feature 对象去返回,用steam的 Send() 方法把它们写入 RouteGuide_ListFeaturesServer。

最后,我们返回了一个 nil 错误告诉 gRPC 响应的写入已经完成。

如果在调用过程中发生任何错误,我们会返回一个非 nil 的错误;

 

客户端流式 RPC

客户端流方法 RecordRoute,我们通过它可以从客户端拿到一个 Point 的流,其中包括我们需要的Point信息。

这次这个方法用了一个 RouteGuide_RecordRouteServer 流,服务器可以用它来同时读 和 写消息——它可以用自己的 Recv() 方法接收客户端消息并且用 SendAndClose() 方法返回它的单个响应。

在方法体中,我们使用 RouteGuide_RecordRouteServer 的 Recv() 方法去反复读取客户端的请求到一个请求对象(在这个场景下是 Point),直到没有更多的消息。

服务器需要在每次调用后检查 Read() 返回的错误。如果返回值为 nil,流依然完好,可以继续读取;

如果返回值为 io.EOF,消息流结束,服务器可以返回它的 RouteSummary。

如果它还有其它值,我们原样返回错误,gRPC 层会把它转换为 RPC 状态。

 

双向流式 RPC

双向流式 RPC RouteChat()。

这里读写的语法和客户端流方法相似,除了服务器会使用流的 Send() 方法而不是 SendAndClose(),因为它需要写多个响应。虽然客户端和服务器端总是会拿到对方写入时顺序的消息,它们可以以任意顺序读写——流的操作是完全独立的。

 

启动服务器

一旦我们实现了所有的方法,我们还需要启动一个gRPC服务器,这样客户端才可以使用服务。

为了构建和启动服务器,我们需要:

  • 使用 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 指定我们期望客户端请求的监听端口。
  • 使用grpc.NewServer()创建 gRPC 服务器的一个实例。
  • 在 gRPC 服务器注册我们的服务实现。
  • 用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用。

 

创建客户端

建立跟服务器的连接

为了调用服务方法,我们首先创建一个 gRPC conn。我们通过给 grpc.Dial() 传入服务器地址和端口号做到这点,如下:

你可以使用 DialOptions 在 grpc.Dial 中设置授权认证(如, TLS,GCE认证,JWT认证),如果服务有这样的要求的话 —— 但是对于 RouteGuide 服务,我们不用这么做。

一旦 gRPC conn 建立起来,我们需要一个client去执行 RPC。我们通过 .proto 生成的 pb 包提供的 NewRouteGuideClient 方法来完成。

调用服务器方法

简单RPC

调用简单 RPC GetFeature 几乎是和调用一个本地方法一样直观。

服务器端流式 RPC

我们给方法传入一个上下文和请求。然而,我们得到返回的是一个 RouteGuide_ListFeaturesClient 实例,而不是一个应答对象。客户端可以使用 RouteGuide_ListFeaturesClient 流去读取服务器的响应。

我们使用 RouteGuide_ListFeaturesClientRecv() 方法去反复读取服务器的响应到一个响应 protocol buffer 对象(在这个场景下是Feature)直到消息读取完毕:每次调用完成时,客户端都要检查从 Recv() 返回的错误 err。如果返回为 nil,流依然完好并且可以继续读取;如果返回为 io.EOF,则说明消息流已经结束;否则就一定是一个通过 err 传过来的 RPC 错误。

客户端流式 RPC

RouteGuide_RecordRouteClient 有一个 Send() 方法,我们可以用它来给服务器发送请求。一旦我们完成使用 Send() 方法将客户端请求写入流,就需要调用流的 CloseAndRecv()方法,让 gRPC 知道我们已经完成了写入同时期待返回应答。我们从 CloseAndRecv() 返回的 err 中获得 RPC 的状态。如果状态为nil,那么CloseAndRecv()的第一个返回值将会是合法的服务器应答。

双向流式 RPC

我们只给函数传入一个上下文对象,拿到可以用来读写的流。

运行例子

服务器端:

$ go run server/server.go

客户端:

$ go run client/client.go

 

参考资料:

中文 gRPC 基础 Go   http://doc.oschina.net/grpc?t=60133 

英文 gRPC 基础 Go  http://www.grpc.io/docs/tutorials/basic/go.html

编写一个go gRPC的服务的更多相关文章

  1. 编写一个简单的TCP服务端和客户端

    下面的实验环境是linux系统. 效果如下: 1.启动服务端程序,监听在6666端口上  2.启动客户端,与服务端建立TCP连接  3.建立完TCP连接,在客户端上向服务端发送消息 4.断开连接 实现 ...

  2. go: 如何编写一个正确的udp服务端

    udp的服务端有一个大坑,即如果收包不及时,在系统缓冲写满后,将大量丢包. 在网上通常的示例中,一般在for循环中执行操作逻辑.这在生产环境将是一个隐患.是的,俺就翻车了. go强大简易的并发能力可以 ...

  3. Viper 微服务框架 编写一个hello world 插件-02

    1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ...

  4. 网络爬虫:使用Scrapy框架编写一个抓取书籍信息的爬虫服务

      上周学习了BeautifulSoup的基础知识并用它完成了一个网络爬虫( 使用Beautiful Soup编写一个爬虫 系列随笔汇总 ), BeautifulSoup是一个非常流行的Python网 ...

  5. 从头开始编写一个Orchard网上商店模块(6) - 创建购物车服务和控制器

    原文地址: http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-pa ...

  6. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...

  7. Java基础---Java---网络编程---TCP的传输、客户端和服务端的互访、建立一个文本转换器、编写一个聊天程序

    演示TCP的传输的客户端和服务端的互访 需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息. 客户端: 1.建立Socket服务,指定要连接方朵和端口 2.获取Socket流中的输出流,将数 ...

  8. .NET Core使用gRPC打造服务间通信基础设施

    一.什么是RPC rpc(远程过程调用)是一个古老而新颖的名词,他几乎与http协议同时或更早诞生,也是互联网数据传输过程中非常重要的传输机制. 利用这种传输机制,不同进程(或服务)间像调用本地进程中 ...

  9. Golang gRPC微服务01: 介绍

    gRPC 是什么 gRPC是goole开源的一个RPC框架和库,支持多语言之间的通信.底层通信采用的是 HTTP2 协议.gRPC在设计上使用了 ProtoBuf 这种接口描述语言.这种IDL语言可以 ...

随机推荐

  1. oracle数据字典-权限-角色

    每个数据库都提供了各自的数据字典的方案,虽然形式不同,但是目的和作用是一样的,比如在mysql里数据字典是在information_schema 里表现的,sqlserver则是在sys这个系统sch ...

  2. docker nodejs 基本应用

    1. 安装docker 环境 2. nodejs  应用布局 package.json { "name": "docker-centos-hello", &qu ...

  3. How to send Email using C#

    try { MailMessage mail = new MailMessage(); SmtpClient SmtpServer = new SmtpClient("smtp.gmail. ...

  4. python3图片裁剪+转换pdf+压缩

    本地大量长图,要发送给别人,所以要对图片进行裁剪+转换pdf+压缩 import zipfile import os from concurrent.futures import ThreadPool ...

  5. python数据类型之str用法

    1.首字母大写 语法:S.capitalize() -> str title = "today is a good day" title_ca = title.capital ...

  6. Windows 8.1 系统上用Oracle VM VirtualBox 安装windows 2008 R2 SP1 的虚拟机 出现 Error Code: 0x000000C4

    Windows 8.1 本来可以安装Hyper-v来安装虚拟机,但是我现在需要使用Oracle VM VirtualBox来安装虚拟机, 所以必须先卸载Hyper-v VirtualBox 安装的虚拟 ...

  7. [nginx]Nginx禁止访问robots.txt防泄漏web目录

    关于robots.txt文件:搜索引擎通过一种程序robot(又称spider),自动访问互联网上的网页并获取网页信 息.您可以在您的网站中创建一个纯文本文件robots.txt,在这个文件中声明该网 ...

  8. WCF 遇到 由于线程退出或应用程序请求,已放弃 I/O 操作 ListenerContextInputStream

    异常类型:IOException 异常消息:An exception has been thrown when reading the stream. 异常信息: at System.ServiceM ...

  9. LeetCode "Count of Smaller Number After Self"

    Almost identical to LintCode "Count of Smaller Number before Self". Corner case needs to b ...

  10. IntelliJ IDEA配置svn

    Settings→Version Control→General 输入svn客户端命令所在的路径   来自为知笔记(Wiz)