我正在尝试使用Akka Streams,以便了解人们应该如何消费TCP服务器从客户端接收的内容(服务器不需要响应客户端)。
这是一个标准的TCP服务器实现(在应用我从@heiko-seeberger的简洁解释here中理解的内容之后):
def runServer(system: ActorSystem, address: String, port: Int, collectingSink: Sink[ByteString,NotUsed]): Unit = {
implicit val sys = system
import system.dispatcher
implicit val materializer = ActorMaterializer()
val handler = Sink.foreach[IncomingConnection] { conn =>
conn.handleWith(
Flow[ByteString]
.via(JsonFraming.objectScanner(maximumObjectLength = 400))
.alsoTo(collectingSink)
.map(b => ByteString.empty)
.filter(_ == false)
)
}
val connections = Tcp().bind(address, port)
val binding = connections.to(handler).run()
binding.onComplete {
case Success(b) =>
println("Server started, listening on: " + b.localAddress)
case Failure(e) =>
println(s"Server could not bind to $address:$port: ${e.getMessage}")
system.terminate()
}
}
我作为 collectingSink 参数传递给runServer()函数的值是这样构造的:
import akka.stream.scaladsl.{Flow, JsonFraming, Sink}
import akka.util.ByteString
import play.api.libs.json.Json
object DeviceDataProcessor {
case class Readings (
radiationLevel: Double,
ambientTemp: Double,
photoSensor: Double,
humidity: Double,
sensorUUID: String,
timestampAttached: Long)
val xformToDeviceReadings = Flow[ByteString]
.via(JsonFraming.objectScanner(maximumObjectLength = 400))
.map(b => {
val jsonified = Json.parse(b.utf8String)
val readings = Readings(
(jsonified \ "radiation_level") .as[Double],
(jsonified \ "ambient_temperature") .as[Double],
(jsonified \ "photosensor") .as[Double],
(jsonified \ "humidity") .as[Double],
(jsonified \ "sensor_uuid") .as[String],
(jsonified \ "timestamp") .as[Long]
)
readings
})
.to(Sink.queue())
}
最后,这就是我运行驱动程序的方式:
object ConsumerDriver extends App {
val actorSystem = ActorSystem("ServerSide")
TCPServer.runServer(actorSystem,"127.0.0.1", 9899,DeviceDataProcessor.xformToDeviceReadings)
}
我没有理解这两件事背后的原因:
1) xformToDeviceReadings 的类型派生为
Sink[ByteStream,NotUsed]
不应该在此处显示映射类型读数吗?
2)我如何开始从这个队列中读取并将元素传递到另一个上游流程?我是否应该首先实现,然后使用物化队列作为我的新源?
我已经浏览了Akka网站的文档。但是,我很乐意被重定向到本文档的任何特定部分或SO上的其他帖子。
请帮助我填补我的概念中的空白。
答案 0 :(得分:0)
1)类型为Sink[ByteString,NotUsed]
,因为在编写实体化值时,Akka Streams会偏向偏差。这意味着您的组合接收器(xformToDeviceReadings
)公开的具体化值是来自其第一阶段的map
,其具体化为NotUsed
。
要公开您想要的物化值,您需要更改为
...
.toMat(Sink.queue())(Keep.right)
请注意,您的接收器类型现在更改为Sink[ByteString, SinkQueueWithCancel[Readings]]
2)与您需要运行流的Readings
队列进行交互,从而获取您的具体化值(队列)并开始从中提取项目。这可能发生在连接处理中:
val handler: Sink[IncomingConnection, Future[Done]] = Flow[IncomingConnection]
.map { conn =>
conn.handleWith(
Flow[ByteString]
.via(JsonFraming.objectScanner(maximumObjectLength = 400))
.alsoToMat(collectingSink)(Keep.right)
.map(_ => ByteString.empty)
.filter(_ == false)
)
}
.mapAsync(1){ queue ⇒
queue.pull()
}
.toMat(Sink.foreach(println))(Keep.right)
请注意,上述解决方案并不理想,尤其是因为实体化队列很可能不线程安全。如果您的目的是将这些Readings
与其他下游流程联系起来,那么最好直接将Sink
拟合用于此目的,而不是通过队列。