接着上期讨论的gRPC unary服务我们跟着介绍gRPC streaming,包括: Server-Streaming, Client-Streaming及Bidirectional-Streaming。我们首先在.proto文件里用IDL描述Server-Streaming服务:

/*
* responding stream of increment results
*/
service SumOneToMany {
rpc AddOneToMany(SumRequest) returns (stream SumResponse) {}
} message SumRequest {
int32 toAdd = ;
} message SumResponse {
int32 currentResult = ;
}

SumOneToMany服务中AddOneToMany函数接受一个SumRequest然后返回stream SumResponse,就这么简单。经过编译后产生了SumOneToManyGrpc.scala文件,在这个文件里提供了有关RPC操作的api。我们看看protoc把IDL描述的服务函数变成了什么样的scala函数:

def addOneToMany(request: SumRequest, responseObserver: StreamObserver[SumResponse]): Unit 

调用scala函数addOneToMany需要传入参数SumRequest和StreamObserver[SumResponse],也就是说用户需要准备这两个入参数。在调用addOneToMany函数时用户事先构建这个StreamObserver传给server,由server把结果通过这个结构传回用户。gRPC是通过StreamObserver类型实例来实现数据streaming的。这个类型的构建例子如下:

    val responseObserver = new StreamObserver[SumResponse] {
def onError(t: Throwable): Unit = println(s"ON_ERROR: $t")
def onCompleted(): Unit = println("ON_COMPLETED")
def onNext(value: SumResponse): Unit =
println(s"ON_NEXT: Current sum: ${value.currentResult}")
}

server端通过onNext把结果不断传回给client端,因为这个responseObserver是在client端构建的。下面是SumManyToMany的实现:

 class SumOne2ManyService extends SumOneToManyGrpc.SumOneToMany {
override def addOneToMany(request: SumRequest, responseObserver: StreamObserver[SumResponse]): Unit = {
val currentSum: AtomicInt = Atomic()
( to request.toAdd).map { _ =>
responseObserver.onNext(SumResponse().withCurrentResult(currentSum.incrementAndGet()))
}
Thread.sleep() //delay and then finish
responseObserver.onCompleted()
}
}

这个addOneToMany服务函数把 1-request.toAdd之间的数字逐个通过responseObserver返还调用方。 在客户端如下调用服务:

    // get asyn stub
val client: SumOneToManyGrpc.SumOneToManyStub = SumOneToManyGrpc.stub(channel)
// prepare stream observer
val streamObserver = new StreamObserver[SumResponse] {
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done incrementing !!!")
override def onNext(value: SumResponse): Unit = println(s"current value: ${value.currentResult}")
}
// call service with stream observer
client.addOneToMany(SumRequest().withToAdd(),streamObserver)

Client-Streaming服务的IDL如下:

/*
* responding a result from a request of stream of numbers
*/
service SumManyToOne {
rpc AddManyToOne(stream SumRequest ) returns (SumResponse) {}
}

传入stream SumRequest, 返回SumResponse。scalaPB自动产生scala代码中的addManyToOne函数款式如下:

def addManyToOne(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest]

调用方提供StreamObserver[SumResponse]用作返回结果,函数返回客方需要的StreamObserver[SumRequest]用以传递request流。注意:虽然在.proto文件中AddManyToOne的返回结果是单个SumResponse,但产生的scala函数则提供了一个StreamObserver[SumResponse]类型,所以需要谨记只能调用一次onNext。下面是这个服务的实现代码:

  class Many2OneService extends SumManyToOneGrpc.SumManyToOne {
val currentSum: AtomicInt = Atomic()
override def addManyToOne(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest] =
new StreamObserver[SumRequest] {
val currentSum: AtomicInt = Atomic()
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done summing!")
override def onNext(value: SumRequest): Unit = {
//only allow one response
if (value.toAdd > )
currentSum.add(value.toAdd)
else
responseObserver.onNext(SumResponse(currentSum.addAndGet(value.toAdd)))
}
}
}

客户方调用示范如下:

    //pass to server for result
val respStreamObserver = new StreamObserver[SumResponse] {
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done responding!")
override def onNext(value: SumResponse): Unit =
println(s"Result: ${value.currentResult}")
}
//get async stub
val client = SumManyToOneGrpc.stub(channel) //get request stream observer from server
val reqStreamObserver = client.addManyToOne(respStreamObserver) List(,,,,).map { n =>
reqStreamObserver.onNext(SumRequest(n))
}

Bidirectional-Streaming的IDL描述如下:

/*
* Sums up numbers received from the client and returns the current result after each received request.
*/
service SumInter {
rpc AddInter(stream SumRequest) returns (stream SumResponse) {}
}

这个service SumInter 描述了stream SumRequest 及 stream SumResponse运算模式。产生的对应scala函数如下:

def addInter(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest]

这个函数的款式与Client-Streaming服务函数是一样的。但是,我们可以通过responseObserver传递多个SumResponse。这个服务的实现代码是这样的:

  class Many2ManyService extends SumInterGrpc.SumInter {
override def addInter(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest] =
new StreamObserver[SumRequest] {
val currentSum: AtomicInt = Atomic()
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done requesting!")
override def onNext(value: SumRequest): Unit = {
responseObserver.onNext(SumResponse(currentSum.addAndGet(value.toAdd)))
}
}
}

我们可以多次调用responseObserver.onNext。客户端源代码如下:

    //create stream observer for result stream
val responseObserver = new StreamObserver[SumResponse] {
def onError(t: Throwable): Unit = println(s"ON_ERROR: $t")
def onCompleted(): Unit = println("ON_COMPLETED")
def onNext(value: SumResponse): Unit =
println(s"ON_NEXT: Current sum: ${value.currentResult}")
}
//get request container
val requestObserver = client.addInter(responseObserver) scheduler.scheduleWithFixedDelay(.seconds, .seconds) {
val toBeAdded = Random.nextInt()
println(s"Adding number: $toBeAdded")
requestObserver.onNext(SumRequest(toBeAdded))
}

下面是本次示范的源代码:

project/scalapb.sbt

addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.18")
libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.7.1"

build.sbt

import scalapb.compiler.Version.scalapbVersion
import scalapb.compiler.Version.grpcJavaVersion name := "learn-gRPC" version := "0.1" scalaVersion := "2.12.6" libraryDependencies ++= Seq(
"com.thesamet.scalapb" %% "scalapb-runtime" % scalapbVersion % "protobuf",
"io.grpc" % "grpc-netty" % grpcJavaVersion,
"com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapbVersion,
"io.monix" %% "monix" % "2.3.0"
) PB.targets in Compile := Seq(
scalapb.gen() -> (sourceManaged in Compile).value
)

src/main/protobuf/sum.proto

syntax = "proto3";

package learn.grpc.services;

/*
* responding stream of increment results
*/
service SumOneToMany {
rpc AddOneToMany(SumRequest) returns (stream SumResponse) {}
} /*
* responding a result from a request of stream of numbers
*/
service SumManyToOne {
rpc AddManyToOne(stream SumRequest ) returns (SumResponse) {}
} /*
* Sums up numbers received from the client and returns the current result after each received request.
*/
service SumInter {
rpc AddInter(stream SumRequest) returns (stream SumResponse) {}
} message SumRequest {
int32 toAdd = ;
} message SumResponse {
int32 currentResult = ;
}

gRPCServer.scala

package learn.grpc.server
import io.grpc.{ServerBuilder,ServerServiceDefinition} trait gRPCServer {
def runServer(service: ServerServiceDefinition): Unit = {
val server = ServerBuilder
.forPort()
.addService(service)
.build
.start // make sure our server is stopped when jvm is shut down
Runtime.getRuntime.addShutdownHook(new Thread() {
override def run(): Unit = server.shutdown()
}) server.awaitTermination()
} }

OneToManyServer.scala

package learn.grpc.sum.one2many.server
import io.grpc.stub.StreamObserver
import learn.grpc.services.sum._
import monix.execution.atomic.{Atomic,AtomicInt}
import learn.grpc.server.gRPCServer object One2ManyServer extends gRPCServer { class SumOne2ManyService extends SumOneToManyGrpc.SumOneToMany {
override def addOneToMany(request: SumRequest, responseObserver: StreamObserver[SumResponse]): Unit = {
val currentSum: AtomicInt = Atomic()
( to request.toAdd).map { _ =>
responseObserver.onNext(SumResponse().withCurrentResult(currentSum.incrementAndGet()))
}
Thread.sleep() //delay and then finish
responseObserver.onCompleted()
}
} def main(args: Array[String]) = {
val svc = SumOneToManyGrpc.bindService(new SumOne2ManyService, scala.concurrent.ExecutionContext.global)
runServer(svc)
} }

OneToManyClient.scala

package learn.grpc.sum.one2many.client
import io.grpc.stub.StreamObserver
import learn.grpc.services.sum._ object One2ManyClient {
def main(args: Array[String]): Unit = { //build connection channel
val channel = io.grpc.ManagedChannelBuilder
.forAddress("LocalHost",)
.usePlaintext(true)
.build() // get asyn stub
val client: SumOneToManyGrpc.SumOneToManyStub = SumOneToManyGrpc.stub(channel)
// prepare stream observer
val streamObserver = new StreamObserver[SumResponse] {
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done incrementing !!!")
override def onNext(value: SumResponse): Unit = println(s"current value: ${value.currentResult}")
}
// call service with stream observer
client.addOneToMany(SumRequest().withToAdd(),streamObserver) // wait for async execution
scala.io.StdIn.readLine()
}
}

ManyToOneServer.scala

package learn.grpc.sum.many2one.server
import io.grpc.stub.StreamObserver
import learn.grpc.services.sum._
import learn.grpc.server.gRPCServer
import monix.execution.atomic.{Atomic,AtomicInt} object Many2OneServer extends gRPCServer {
class Many2OneService extends SumManyToOneGrpc.SumManyToOne {
val currentSum: AtomicInt = Atomic()
override def addManyToOne(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest] =
new StreamObserver[SumRequest] {
val currentSum: AtomicInt = Atomic()
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done summing!")
override def onNext(value: SumRequest): Unit = {
//only allow one response
if (value.toAdd > )
currentSum.add(value.toAdd)
else
responseObserver.onNext(SumResponse(currentSum.addAndGet(value.toAdd)))
}
}
} def main(args: Array[String]): Unit = {
val svc = SumManyToOneGrpc.bindService(new Many2OneService,scala.concurrent.ExecutionContext.global)
runServer(svc)
}
}

ManyToOneClient.scala

package learn.grpc.sum.many2one.client
import io.grpc.stub.StreamObserver
import learn.grpc.services.sum._ object Many2OneClient {
def main(args: Array[String]): Unit = {
//build channel
val channel = io.grpc.ManagedChannelBuilder
.forAddress("LocalHost",)
.usePlaintext(true)
.build()
//pass to server for result
val respStreamObserver = new StreamObserver[SumResponse] {
override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}")
override def onCompleted(): Unit = println("Done responding!")
override def onNext(value: SumResponse): Unit =
println(s"Result: ${value.currentResult}")
}
//get async stub
val client = SumManyToOneGrpc.stub(channel) //get request stream observer from server
val reqStreamObserver = client.addManyToOne(respStreamObserver) List(,,,,).map { n =>
reqStreamObserver.onNext(SumRequest(n))
}
scala.io.StdIn.readLine()
}
}

ManyToManyServer.scala

package learn.grpc.sum.many2many.server
import io.grpc.stub.StreamObserver
import learn.grpc.services.sum._
import learn.grpc.server.gRPCServer
import monix.execution.atomic.{Atomic,AtomicInt}
object Many2ManyServer extends gRPCServer {
class Many2ManyService extends SumInterGrpc.SumInter {
override def addInter(responseObserver: StreamObserver[SumResponse]): StreamObserver[SumRequest] =
new StreamObserver[SumRequest] {
val currentSum: AtomicInt = Atomic() override def onError(t: Throwable): Unit = println(s"error: ${t.getMessage}") override def onCompleted(): Unit = println("Done requesting!") override def onNext(value: SumRequest): Unit = {
responseObserver.onNext(SumResponse(currentSum.addAndGet(value.toAdd)))
}
}
}
def main(args: Array[String]): Unit = {
val svc = SumInterGrpc.bindService(new Many2ManyService, scala.concurrent.ExecutionContext.global)
runServer(svc)
} }

ManyToManyClient.scala

package learn.grpc.sum.many2many.client
import monix.execution.Scheduler.{global => scheduler}
import learn.grpc.services.sum._ import scala.concurrent.duration._
import scala.util.Random
import io.grpc._
import io.grpc.stub.StreamObserver object Many2ManyClient {
def main(args: Array[String]): Unit = {
val channel = ManagedChannelBuilder.forAddress("localhost", ).usePlaintext(true).build
val client = SumInterGrpc.stub(channel)
//create stream observer for result stream
val responseObserver = new StreamObserver[SumResponse] {
def onError(t: Throwable): Unit = println(s"ON_ERROR: $t")
def onCompleted(): Unit = println("ON_COMPLETED")
def onNext(value: SumResponse): Unit =
println(s"ON_NEXT: Current sum: ${value.currentResult}")
}
//get request container
val requestObserver = client.addInter(responseObserver) scheduler.scheduleWithFixedDelay(.seconds, .seconds) {
val toBeAdded = Random.nextInt()
println(s"Adding number: $toBeAdded")
requestObserver.onNext(SumRequest(toBeAdded))
} scala.io.StdIn.readLine()
} }

ScalaPB(3): gRPC streaming的更多相关文章

  1. ScalaPB(2): 在scala中用gRPC实现微服务

    gRPC是google开源提供的一个RPC软件框架,它的特点是极大简化了传统RPC的开发流程和代码量,使用户可以免除许多陷阱并聚焦于实际应用逻辑中.作为一种google的最新RPC解决方案,gRPC具 ...

  2. ScalaPB(0): 找寻合适的内部系统微服务集成工具

    前一段时间我们探讨了SDP的一个基于集群的综合数据平台解决方案,由多种数据库组成,包括:JDBC, Cassandra 及MongoDB.其中Cassandra和MongoDB属于分布式数据库,可以在 ...

  3. ScalaPB(5):用akka-stream实现reactive-gRPC

      在前面几篇讨论里我们介绍了scala-gRPC的基本功能和使用方法,我们基本确定了选择gRPC作为一种有效的内部系统集成工具,主要因为下面gRPC支持的几种服务模式: .Unary-Call:独立 ...

  4. ScalaPB(4): 通用跨系统protobuf数据,sbt设置

    我们知道,在集群环境节点之间进行交换的数据必须经过序列化/反序列化处理过程,而在这方面protobuf是一个比较高效.易用的模式.用户首先在.proto文件中用IDL来定义系统中各种需要进行交换的数据 ...

  5. SDP(12): MongoDB-Engine - Streaming

    在akka-alpakka工具包里也提供了对MongoDB的stream-connector,能针对MongoDB数据库进行streaming操作.这个MongoDB-connector里包含了Mon ...

  6. ScalaPB(1): using protobuf in akka

    任何类型的实例作为消息在两端独立系统的机器之间进行传递时必须经过序列化/反序列化serialize/deserialize处理过程.假设以下场景:在一个网络里有两台连接的服务器,它们分别部署了独立的a ...

  7. gRPC(2):客户端创建和调用原理

    1. gRPC 客户端创建流程 1.1 背景 gRPC 是在 HTTP/2 之上实现的 RPC 框架,HTTP/2 是第 7 层(应用层)协议,它运行在 TCP(第 4 层 - 传输层)协议之上,相比 ...

  8. Spark2.2(三十三):Spark Streaming和Spark Structured Streaming更新broadcast总结(一)

    背景: 需要在spark2.2.0更新broadcast中的内容,网上也搜索了不少文章,都在讲解spark streaming中如何更新,但没有spark structured streaming更新 ...

  9. gRPC(2):四种基本通信模式

    在 gRPC(1):入门及简单使用(go) 中,我们实现了一个简单的 gRPC 应用程序,其中双方通信是简单的请求-响应模式,没发出一个请求都会得到一个响应,然而,借助 gRPC 可以实现不同的通信模 ...

随机推荐

  1. PA 模块常用表2

    SELECT * FROM pa_expenditure_items_all  项目支出 select *from pa_cost_distribution_lines_all  支出分配行 SELE ...

  2. [问与答]怎样在 Android Stuido中删除一个project

    sof Remove Project from Android Studio 问 第一次用Android Stuido,建立一个项目,却不知道怎么删除? 答 大概有3种方式 方式一 (简单实用) 点击 ...

  3. Java 开源 CMS :magnolia

    Magnolia 是一个开源基于Java的Web内容管理系统(CMS),构建在Java内容知识库标准(JSR-170).在使用它的过程中,我发现它的界面确实很有特色:给人一种Win8的感觉.在此记录一 ...

  4. Android的fuzz测试技术之符号执行浅谈-android学习之旅(82)

    简单的漏洞越来越少,需要改进目前的方法 : 通过符号执行,得出执行路径,然后在进行fuzzy是较为有效的方法之一 1)为待测单元自动地生成可到达的测试数据,即提高测试目标的覆盖率 2)根据特定的漏洞模 ...

  5. 网站开发进阶(二十五)js如何将html表格导出为excel文件

    js如何将html表格导出为excel文件        赠人玫瑰,手留余香.若您感觉此篇博文对您有用,请花费2秒时间点个赞,您的鼓励是我不断前进的动力,共勉! jsp页面数据导出成excel的方法很 ...

  6. Collections.sort()的分析

    首先我们得说明在Collections里面有两个排序方法 public static <T> void sort(List<T> list, Comparator<? s ...

  7. MTK 软件设置路径

    1. uboot路径 mediatek\custom\common\uboot\logo\hvga\hvga_kernel.bmp mediatek\custom\common\uboot\logo\ ...

  8. StarUML配置Word生成文档模板

    来源:fasiondog 许多UML建模工具可以自动生成文档,让需求人员.开发人员专心于需求.设计的建模.当然为了能够生成符合自己要求的模板,需对建模时的目录结构(模型和包)有所规划和要求,否则很难生 ...

  9. ActiveX数据对象之事务控制在VB和DELPHI中的应用

            本文发表在中国人民解放军"信息工程大学"学报 2001年第3期.        ActiveX数据对象之事务控制在VB和DELPHI中的应用             ...

  10. plsql 导入导出表、数据、序列、视图

     一.导出: 1.打开plsql-->工具---->导出用户对象(可以导出表结构和序列.视图) ps:如果上面不选中"包括所有者",这样到导出的表结构等就不包含所有 ...