前置条件:

获取 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. 虚拟化之vmware-网络

    http://blog.sina.com.cn/s/blog_6b89db7a01012jtw.htmlESXI 5.0 虚拟机的网络适配器兼容性列表 就需要在vSphere标准交换机(vSphere ...

  2. 九度OJ题目1387斐波那契数列

    /*斐波那契数列,又称黄金分割数列,指的是这样一个数列: 0.1.1.2.3.5.8.13.21.…… 在数学上,斐波纳契数列被定义如下: F0=0,F1=1, Fn=F(n-1)+F(n-2)(n& ...

  3. SELECT INTO 和 INSERT INTO SELECT 两种表复制语句.txt

    Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) values(value1,value2,...)这种形式的在应用程序开发中必不可少.但我 ...

  4. json字符串转map

    <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</ar ...

  5. ORACLE 事务学习

    以下内容为本人的学习手记,有不足和理解错误的地方,请谨慎参考. 在ORACLE中的事务并不像MSSQL中的事务那样可以随意控制. ORACLE的事务是在进行数据第一次被修改后自动开启的无法显示的开启事 ...

  6. C#子线程刷新界面并关闭窗体

    目的:要循环刷新界面上的控件,同时不影响用户操作.循环结束后关闭窗体. 步骤:先创建一个窗体,窗体中拖入一个lable控件(label1),一个button控件(button1) 代码窗口输入: // ...

  7. Java 开发环境搭建

    找到一篇很不錯的Java開發環境搭建的博客, 原文地址為:http://www.cnblogs.com/bribe/p/3377008.html Java 开发环境搭建 一.开发工具获取 1.开发工具 ...

  8. 用Opera Mobile调试手机版网页【转】

    注意:新版本的opera已经采用webkit内核,没有dragonfly了. 要下载12版的http://get.geo.opera.com/pub/opera/win/1216/int/Opera_ ...

  9. [转]Hibernate Session各种状态转换方法分析

    摘自http://spiritfrog.iteye.com/blog/221177 我的印象里, Hibernate session中常用的保存操作只有:save, update, saveOrUpd ...

  10. UIPickerView自定义背景

    #import <UIKit/UIKit.h> @interface MyPicker : UIPickerView { } @end -------------------------- ...