播放框架Akka流WebSocket处理消息发送到Deadletters

时间:2018-09-28 14:06:28

标签: scala playframework akka

我正在努力将注意力集中在akka流和处理Web套接字的方法上,但是我有些事情很清楚。 首先,我正在尝试完成从某些客户端到服务器的单向通信以及同一服务器与其他客户端之间的单向通信。

client1 -----> Server <------> client2

我正在查看提供的示例here。 产生的代码如下所示:

1)从控制器开始

class Test @Inject()(@Named("connManager") myConnectionsManager: ActorRef, cc: ControllerComponents)
                (implicit val actorSystem: ActorSystem,
                 val mat: Materializer,
                 implicit val executionContext: ExecutionContext)
extends AbstractController(cc) {


private def wsFutureFlow(id: String): Future[Flow[String, String, NotUsed]] = {
    implicit val timeout: Timeout = Timeout(5.seconds)
    val future = myConnectionsManager ? CreateRemote(id)
    val futureFlow = future.mapTo[Flow[String, String, NotUsed]]
    futureFlow
}

private def wsFutureLocalFlow: Future[Flow[String, String, NotUsed]] =   {
    implicit val timeout: Timeout = Timeout(5.seconds)
    val future = myConnectionsManager ? CreateLocal
    val futureFlow = future.mapTo[Flow[String, String, NotUsed]]
    futureFlow
}

def ws: WebSocket = WebSocket.acceptOrResult[String, String] {

    rh =>
        wsFutureFlow(rh.id.toString).map { flow =>
            Right(flow)
        }
}

def wsLocal: WebSocket = WebSocket.acceptOrResult[String, String] {

    _ =>
        wsFutureLocalFlow.map { flow =>
            Right(flow)
        }
}
}

关于连接管理器参与者。这相当于示例中的UserParentActor。

class MyConnectionsManager @Inject()(childFactory: MyTestActor.Factory)
                                (implicit ec: ExecutionContext, mat: Materializer) extends Actor with InjectedActorSupport {

import akka.pattern.{ask, pipe}

implicit val timeout: Timeout = Timeout(2.seconds)

override def receive: Receive = {
    case CreateRemote(x) =>
        val child = injectedChild(childFactory(), s"remote-$x")
        context.watch(child)
        privatePipe(child)
    case CreateLocal =>
        val child = injectedChild(childFactory(), "localConnection")
        context.become(onLocalConnected(child))
        privatePipe(child)
    case Terminated(child) =>
        println(s"${child.path.name} terminated...")
}

def onLocalConnected(local: ActorRef): Receive = {
    case CreateRemote(x) =>
        val child = injectedChild(childFactory(), s"remote-$x")
        context.watch(child)
        privatePipe(child)
    case x: SendToLocal => local ! x
}

private def privatePipe(child: ActorRef) = {
    val future = (child ? Init).mapTo[Flow[String, String, _]]
    pipe(future) to sender()
    () // compiler throws exception without this: non-unit value discarded
    }
}

MyTestActor看起来像这样:

class MyTestActor @Inject()(implicit mat: Materializer, ec: ExecutionContext) extends Actor {

val source: Source[String, Sink[String, NotUsed]] = MergeHub.source[String]
  .recoverWithRetries(-1, { case _: Exception => Source.empty })

private val jsonSink: Sink[String, Future[Done]] = Sink.foreach { json =>
    println(s"${self.path.name} got message:  $json")

    context.parent ! SendToLocal(json)
}

private lazy val websocketFlow: Flow[String, String, NotUsed] = {
    Flow.fromSinkAndSourceCoupled(jsonSink, source).watchTermination() { (_, termination) =>
        val name = self.path.name
        termination.foreach(_ => context.stop(self))
        NotUsed
    }
}

def receive: Receive = {

    case Init =>
        println(s"${self.path.name}: INIT")
        sender ! websocketFlow
    case SendToLocal(x) =>
        println(s"Local got from remote: $x")
    case msg: String => sender ! s"Actor got message: $msg"
    }
}

除了汇和源实际上如何连接到参与者外,我不了解的还有以下内容。当我启动系统时,我会向演员发送一些消息。但是,当我关闭与名为remote的actor的连接,并继续向名为“ localConnection”的actor发送消息后,这些消息将发送到DeadLetters:

[info] Done compiling.
[info] 15:49:20.606 - play.api.Play - Application started (Dev)
localConnection: INIT
localConnection got message:  test data
Local got from remote: test data
localConnection got message:  hello world
Local got from remote: hello world
remote-133: INIT
remote-133 got message:  hello world
Local got from remote: hello world
remote-133 got message:  hello from remote
Local got from remote: hello from remote
[error] 15:50:24.449 - a.a.OneForOneStrategy - Monitored actor [Actor[akka://application/user/connManager/remote-133#-998945083]] terminated
akka.actor.DeathPactException: Monitored actor [Actor[akka://application/user/connManager/remote-133#-998945083]] terminated
deadLetters got message:  hello local   

我认为这是由于引发异常而引起的。。。有人可以向我解释为何将消息发送到DeadLetters吗? 除此之外,我想知道为什么在privatePipe末尾没有返回“()”的情况下,为什么不断得到编译器异常吗?

还有,我应该做些不同的事情吗?

1 个答案:

答案 0 :(得分:0)

我意识到由于在MyConnectionsManager actor的新行为中忘记处理Terminated消息而引发了异常。

def onLocalConnected(local: ActorRef): Receive = {
    case CreateRemote(x) =>
        val child = injectedChild(childFactory(), s"remote-$x")
        context.watch(child)
        privatePipe(child)
    case Terminated(child) => println(s"${child.path.name} terminated...")
    case x: SendToLocal => local ! x
}

它现在似乎正在工作。