将TCP服务器的输出捕获到Akka Stream队列的最佳推荐方法是什么?

时间:2017-04-27 02:35:00

标签: scala tcp akka-stream

我正在尝试使用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上的其他帖子。

请帮助我填补我的概念中的空白。

1 个答案:

答案 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拟合用于此目的,而不是通过队列。