如何通过Akka HTTP WebSocket发送活动WebSocket连接的实时计数? (仅适用于Akka Streams)

时间:2017-09-06 19:04:23

标签: akka akka-stream akka-http

如何通过Akka WebSocket发送活动WebSocket连接的实时计数?似乎Akka HTTP WebSockets改变了Akka Stream的前置阶段的运行。

在下面的第一个代码块中,只有在唯一连接时才会立即发送计数。任何已经计数超过1的客户端在下一个客户端连接之前都不会收到更新。

第二个代码块删除了WebSocket代码,因此只使用了Akka Streams,并且每个流订阅 都会收到立即计数,如stdout所示。

使用Akka HTTP WebSocket

运行此代码并使用多个浏览器窗口访问http://localhost:8080会在浏览器控制台中显示此信息。

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.TextMessage
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Sink, Source}
import akka.stream.{ActorMaterializer, OverflowStrategy}

import scala.concurrent.ExecutionContextExecutor

object CounterFlow {

    private implicit val actorSystem: ActorSystem = ActorSystem("CounterFlowTest")
    private implicit val materializer: ActorMaterializer = ActorMaterializer()
    private implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher

    val (counterSourceQueue, counterSource) =
        Source.queue[Int](0, OverflowStrategy.backpressure)
            .conflate(_ + _)
            .scan(0)(_ + _)
            .toMat(BroadcastHub.sink(bufferSize = 1))(Keep.both)
            .run()

    val clientFlow: Source[TextMessage.Strict, Unit] =
        counterSource
            .map(_.toString)
            .map(TextMessage.Strict)
            .prepend(Source.fromIterator(() => {
                counterSourceQueue.offer(1)
                Iterator.empty
            }))
            .watchTermination()((_, done) => done.foreach(_ => counterSourceQueue.offer(-1)))

    def main(args: Array[String]): Unit = {

        val route: Route =
            pathEndOrSingleSlash {
                get {
                    complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,
                        "<h1>Check browser console for connection count</h1>" +
                            "<script>const ws = new WebSocket(\"ws://localhost:8080/ws\");ws.onmessage = e => console.log(e.data);</script>"))
                }
            } ~
                path("ws") {
                    handleWebSocketMessages(
                        Flow.fromSinkAndSourceCoupled(
                            Sink.ignore,
                            clientFlow))
                }

        Http().bindAndHandle(route, "0.0.0.0", 8080)
        () // discard non-Unit value
    }
}

仅限Akka Streams

如在stdout上看到的,每个客户端都会根据需要立即收到计数。

import akka.actor.ActorSystem
import akka.stream.scaladsl.{BroadcastHub, Keep, Sink, Source}
import akka.stream.{ActorMaterializer, OverflowStrategy}

import scala.concurrent.ExecutionContext.Implicits.global

object CounterFlow {

    private implicit val actorSystem: ActorSystem = ActorSystem("CounterFlowTEst")
    private implicit val materializer: ActorMaterializer = ActorMaterializer()

    val (counterSourceQueue, counterSource) = Source.queue[Int](0, OverflowStrategy.backpressure)
        .conflate(_ + _)
        .scan(0)(_ + _)
        .toMat(BroadcastHub.sink)(Keep.both)
        .run()

    def subscribeClient(clientName: String): Unit =
        counterSource
            .prepend(Source.fromIterator(() => {
                counterSourceQueue.offer(1)
                Iterator.empty
            }))
            .watchTermination()((_, done) => done.foreach(_ => counterSourceQueue.offer(-1)))
            .runWith(Sink.foreach(msg => println(s"$clientName $msg")))
            .foreach(_ => println(s"$clientName Done"))

    def main(args: Array[String]): Unit = {
        subscribeClient("A")
        subscribeClient("B")
        Thread.sleep(500L)
        subscribeClient("C")
    }
}

1 个答案:

答案 0 :(得分:0)

通过将BroadcastHub.sink(bufferSize = 1)更改为BroadcastHub.sink(因为它位于Akka Streams版本中),每个客户端都会立即收到计数。

这是我测试过的版本:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.TextMessage
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Sink, Source}
import akka.stream.{ActorMaterializer, OverflowStrategy}

import scala.concurrent.ExecutionContextExecutor

object CounterFlow {

    private implicit val actorSystem: ActorSystem = ActorSystem("CounterFlowTest")
    private implicit val materializer: ActorMaterializer = ActorMaterializer()
    private implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher

    val (counterSourceQueue, counterSource) =
        Source.queue[Int](0, OverflowStrategy.backpressure)
            .conflate(_ + _)
            .scan(0)(_ + _)
            .toMat(BroadcastHub.sink)(Keep.both)
            .run()

    val clientFlow: Source[TextMessage.Strict, Unit] =
        counterSource
            .map(_.toString)
            .map(TextMessage.Strict)
            .prepend(Source.fromIterator(() => {
                counterSourceQueue.offer(1)
                Iterator.empty
            }))
            .watchTermination()((_, done) => done.foreach(_ => counterSourceQueue.offer(-1)))

    def main(args: Array[String]): Unit = {

        val route: Route =
            pathEndOrSingleSlash {
                get {
                    complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,
                        "<p>Connections: <span id='c'>?</span></p>" +
                            "<script>const ws = new WebSocket(\"ws://localhost:8080/ws\");ws.onmessage = e => { console.log(e.data); document.getElementById('c').innerText = e.data; };</script>"))
                }
            } ~
                path("ws") {
                    handleWebSocketMessages(
                        Flow.fromSinkAndSourceCoupled(
                            Sink.ignore,
                            clientFlow))
                }

        Http().bindAndHandle(route, "0.0.0.0", 8080)
        () // discard non-Unit value
    }
}

PS:非常好的例子!