PlayFramework - Actor 對接

playframework 本身就是akka、那如何對接其它的akka系統呢、也就是發消息給其它系統、或者其它系統連接playframework的akka。由於網上的demo幾乎是沒有的、在沒有API的條件下研究了一週終於搞定了。

使用指南

使用的版本是Play 2.7.x

libraryDependencies ++= Seq(
  guice,
  ws,
  filters,
  "org.scalatestplus.play" %% "scalatestplus-play" % "4.0.1" % Test,
  "com.typesafe.akka" %% "akka-actor" % "2.5.21",
  "com.typesafe.akka" %% "akka-slf4j" % "2.5.21",
  "com.typesafe.akka" %% "akka-remote" % "2.5.21",
  "com.typesafe.akka" %% "akka-stream" % "2.5.21",
  "com.typesafe.akka" %% "akka-protobuf" % "2.5.21"
)

添加下面配置到application.conf、很重要

akka {
  actor {
    warn-about-java-serializer-usage = false
    provider = "akka.remote.RemoteActorRefProvider"
  }

  remote {
    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = "localhost"
      port = 2553
    }
  }
}

play.akka.actor-system = "demo"

actor.debug.receive = on
actor.default-dispatcher.fork-join-executor.pool-size-max = 64

Controller 即可以作爲服務端也可以作爲客戶端

@Singleton
class ActorController @Inject()(cc: ControllerComponents, actorSystem: ActorSystem)(implicit ec: ExecutionContext)
  extends AbstractController(cc) {

  val testActor: ActorRef = actorSystem.actorOf(Props[HelloActor], "hello")
  implicit val timeout: Timeout = 3.seconds

  def PING(): Action[AnyContent] = Action.async {
    (testActor ? Message.PING).mapTo[String].map(message => Ok(message))
  }

  def send(): Action[AnyContent] = Action {
    actorSystem.actorOf(Props(new HiActor()))
    Ok("success")
  }


  def hi: Action[AnyContent] = Action {
    Ok("hi")
  }

}

作爲服務端的Actor

class ServerActor extends Actor with ActorLogging {
  override def receive: Receive = {
    case Message.PING(msg: String) =>
      println(s"收到客戶端發來的消息:$msg")
      sender() ! Message.PONG("我是服務端")
  }
}

作爲客戶端的Actor

class ClientActor extends Actor with ActorLogging {

  override def preStart(): Unit = {
    val actor = context.actorSelection(s"akka.tcp://ServerSystem@localhost:2000/user/ServerActor")
    actor ! Message.PING("我是客戶端")
  }

  override def receive: Receive = {
    case Message.PONG(msg: String) =>
      println(s"收到服務端發來的消息:$msg")
  }
}

注意

PS:測試之前一定要訪問 http://localhost:9000/init
因爲在開發環境下、是需要訪問之後纔會進行編譯的、不然如下警告

[INFO] [05/31/2019 12:47:49.069] [main] [akka.remote.Remoting] Starting remoting
[INFO] [05/31/2019 12:47:49.238] [main] [akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://LocalSystem@localhost:57285]
[INFO] [05/31/2019 12:47:49.240] [main] [akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://LocalSystem@localhost:57285]
[WARN] [05/31/2019 12:47:49.434] [LocalSystem-akka.remote.default-remote-dispatcher-5] [akka.tcp://LocalSystem@localhost:57285/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2Fdemo%40localhost%3A2553-0] Association with remote system [akka.tcp://demo@localhost:2553] has failed, address is now gated for [5000] ms. Reason: [Association failed with [akka.tcp://demo@localhost:2553]] Caused by: [java.net.ConnectException: Connection refused: localhost/127.0.0.1:2553]
[WARN] [05/31/2019 12:47:49.434] [New I/O boss #3] [NettyTransport(akka://LocalSystem)] Remote connection to [null] failed with java.net.ConnectException: Connection refused: localhost/127.0.0.1:2553
[INFO] [05/31/2019 12:47:49.437] [LocalSystem-akka.actor.default-dispatcher-3] [akka://LocalSystem/deadLetters] Message [controllers.Message$PING] from Actor[akka://LocalSystem/user/LocalActor#1787938353] to Actor[akka://LocalSystem/deadLetters] was not delivered. [1] dead letters encountered. If this is not an expected behavior, then [Actor[akka://LocalSystem/deadLetters]] may have terminated unexpectedly, This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

測試

客戶端連接服務端、發送消息給playframework


object ActorNodeTest {

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

    val str: String =
      """
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.actor.warn-about-java-serializer-usage = off
        |akka.remote.netty.tcp.hostname = localhost
        |akka.remote.netty.tcp.port = 0
      """.stripMargin
    val conf = ConfigFactory.parseString(str)
    val actorSystem = ActorSystem("LocalSystem", conf)
    actorSystem.actorOf(Props(new Actor() {

      override def preStart(): Unit = {
        val a = context.actorSelection("akka.tcp://demo@localhost:2553/user/hello")
        a ! Message.PING("我是客戶端")
      }

      override def receive: Receive = {
        case Message.PONG(msg:String) => println(s"收到服務器發來的消息:$msg")
      }
    }), "LocalActor")
  }

}

作爲服務端啓動讓playframework發來消息

object ActorServerTest {

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

    val str: String =
      """
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.actor.warn-about-java-serializer-usage = off
        |akka.remote.netty.tcp.hostname = localhost
        |akka.remote.netty.tcp.port = 2000
      """.stripMargin
    val conf = ConfigFactory.parseString(str)
    val actorSystem = ActorSystem("ServerSystem", conf)
    actorSystem.actorOf(Props(new Actor() {
      override def receive: Receive = {
        case Message.PING(msg: String) =>
          println(s"收到客戶端發來的消息:$msg")
          sender() ! Message.PONG("我收到了")
      }
    }), "ServerActor")
  }

}

成功結果如下

client -> server

[INFO] [05/31/2019 12:49:43.192] [main] [akka.remote.Remoting] Starting remoting
[INFO] [05/31/2019 12:49:43.366] [main] [akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://LocalSystem@localhost:57770]
[INFO] [05/31/2019 12:49:43.367] [main] [akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://LocalSystem@localhost:57770]
收到服務器發來的消息:我是服務端

[info] play.api.Play - Application started (Dev) (no global state)
收到客戶端發來的消息:我是客戶端

server <- client

[INFO] [05/31/2019 12:50:39.695] [main] [akka.remote.Remoting] Starting remoting
[INFO] [05/31/2019 12:50:39.868] [main] [akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://ServerSystem@localhost:2000]
[INFO] [05/31/2019 12:50:39.869] [main] [akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://ServerSystem@localhost:2000]
收到客戶端發來的消息:我是客戶端

[info] play.api.Play - Application started (Dev) (no global state)
收到服務端發來的消息:我收到了

項目源碼 https://github.com/dounine/play-actor

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章