Scala 使用Akka参与者处理多个TCP连接

Scala 使用Akka参与者处理多个TCP连接,scala,tcp,akka,actor,Scala,Tcp,Akka,Actor,我正在尝试使用actors设置一个简单的TCP服务器,它应该允许同时连接多个客户端。我将问题简化为以下简单程序: package actorfail import akka.actor._, akka.io._, akka.util._ import scala.collection.mutable._ import java.net._ case class Foo() class ConnHandler(conn: ActorRef) extends Actor { def rece

我正在尝试使用actors设置一个简单的TCP服务器,它应该允许同时连接多个客户端。我将问题简化为以下简单程序:

package actorfail
import akka.actor._, akka.io._, akka.util._
import scala.collection.mutable._
import java.net._

case class Foo()

class ConnHandler(conn: ActorRef) extends Actor {
  def receive = {
    case Foo() => conn ! Tcp.Write(ByteString("foo\n"))
  }
}

class Server(conns: ArrayBuffer[ActorRef]) extends Actor {
  import context.system
  println("Listing on 127.0.0.1:9191")
  IO(Tcp) ! Tcp.Bind(self, new InetSocketAddress("127.0.0.1", 9191))
  def receive = {
    case Tcp.Connected(remote, local) =>
      val handler = context.actorOf(Props(new ConnHandler(sender)))
      sender ! Tcp.Register(handler)
      conns.append(handler)
  }
}

object Main {
  def main(args: Array[String]) {
    implicit val system = ActorSystem("Test")
    val conns = new ArrayBuffer[ActorRef]()
    val server = system.actorOf(Props(new Server(conns)))
    while (true)  {
      println(s"Sending some foos")
      for (c <- conns) c ! Foo()
      Thread.sleep(1000)
    }
  }
}

我理解这意味着我们尝试向其发送
Tcp.Write
命令的目标参与者不再接受消息。但为什么呢?你能帮我理解根本问题吗?如何实现这一点?

上述代码存在两个问题:

  • 在actor消息中发送可变状态并以非线程安全的方式对其进行变异
  • 在道具中包含不稳定的引用

在我详细阐述之前,请考虑阅读文档,这都包括在这里。

可变消息 ArrayBuffer不是线程安全的,但是您可以将它从主例程传递给不同的参与者,然后由他们独立(并发)修改它。这将导致更新丢失或数据结构本身损坏。另一方面,如果没有适当的同步,就不能保证主线程会看到修改,因为编译器原则上可以确定缓冲区在
循环期间不会发生变化,并相应地优化代码

参与者只发送消息,而不依赖共享的可变状态。在这种情况下,解决方案是将
while
循环提升到一个actor中(但在一秒钟后将消息调度到
self
,而不是阻塞
线程.sleep(1000)
调用)。然后,连接处理程序只需将
ActorRef
传递给此
foo
发送方参与者,他们将向其发送一条消息以注册自己,然后该参与者将活动连接的列表保存在其封装范围内。这样做的好处是,您可以使用DeathWatch在连接终止时删除连接

道具中的不稳定引用 有问题的代码:
Props(新的ConnHandler(发送者))

道具是从一个actor工厂构建的,在本例中,它作为一个by-name参数;整个
new
表达式将在以后的某个时间进行计算,只要这样的参与者可能在不同的线程上初始化。这意味着,
sender
也将在以后从该执行上下文进行评估,因此它很可能是
deadLetters
(如果父参与者当前未运行,如果是,则
sender
很可能指向错误的参与者)


这里的解决方案是有文档记录的。

参与者内的可变状态是可以的,但看起来您正试图从参与者外部访问服务器参与者的连接。这可不是什么好东西。为什么不让服务器管理连接池并在这些连接上发送消息?@Gagstead我刚刚测试了使用一个参与者发送foo,这似乎有效,您能否详细说明这方面的技术问题,或者这方面的文档记录在哪里?我希望有一种方法可以将参与者与其他并发模型混合使用。实际上,从外部访问actor的代码是一个循环,以阻塞方式从
读取行。当然,我可以将其转换为一个参与者,但将其表述为一个状态机有点笨重。我更希望有一种从外部发送消息的方式,只考虑ActorRef。你从Akka项目负责人那里得到了答案。他提供了一些文档链接。我不认为你能像你所希望的那样混淆并发模型。@Gagstead根据他的回答我能:)我只在一个位置修改ArrayBuffer,我的印象是一个参与者一次只能处理一条消息?当然它仍然是坏的,因为有另一个参与者读取数组,但这仅仅是为了演示,与真实程序中的情况不同。不过,您已经发现了一个更有趣的问题,稍后我将尝试解决方案:)即使您只在一个位置修改数组(您是对的,在这种特殊的、狭窄的情况下实际上是安全的),外部观察者可能会看到缓冲区处于不一致的状态,甚至可能会“向后”执行步骤当参与者在线程和CPU内核之间跳转时。共享可变状态不仅仅是一类显而易见的问题;-)这只是一个简单的例子,我在真实的程序中不做任何这样的事情。关闭构造函数参数上的道具有效,谢谢!
Sending some foos
[INFO] [03/27/2015 21:24:07.331] [Test-akka.actor.default-dispatcher-6] [akka://Test/deadLetters] Message [akka.io.Tcp$Write] from Actor[akka://Test/user/$a/$b#-308726290] to Actor[akka://Test/deadLetters] was not delivered. [7] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.