Scala 在Play Framework WebSocket中广播消息

Scala 在Play Framework WebSocket中广播消息,scala,websocket,playframework-2.0,playframework-2.2,iterate,Scala,Websocket,Playframework 2.0,Playframework 2.2,Iterate,我正在Play Framework WebSockets中使用Concurrent.unicast[JsValue]推送消息,我希望优化向多个用户发送相同消息的过程。是否可以以某种方式使用多个并发频道?简短回答 长答案 package controllers import akka.actor.Actor import play.api.libs.iteratee.Enumerator import play.api.libs.iteratee.Concurrent.Channel impo

我正在Play Framework WebSockets中使用
Concurrent.unicast[JsValue]
推送消息,我希望优化向多个用户发送相同消息的过程。是否可以以某种方式使用多个
并发频道

简短回答


长答案

package controllers

import akka.actor.Actor
import play.api.libs.iteratee.Enumerator
import play.api.libs.iteratee.Concurrent.Channel
import play.api.libs.iteratee.Concurrent
import play.api.Logger
import play.api.libs.iteratee.Iteratee
import play.api.libs.concurrent.Execution.Implicits.defaultContext

object AdvancedRoomMessages {
  case class Join(name: String)
  case class BroadcastGroup(msg: String, gName: String)
  case class BroadcastAll(msg: String)
  case class AddGroup(gName: String)
  case class RemoveGroup(gName: String)
  case class AddUserToGroup(userName: String, gName: String)
  case class removeUserFromGroup(userName: String, gName: String)
}


class AdvancedRoom extends Actor {
  import scala.collection.mutable._


  /**
   * common channel for communication
   */


  val (enumerator, channel) = Concurrent.broadcast[String]



  /**
   * every user has his own channel
   */
  val users = Map[String, (Enumerator[String],Channel[String])]()

  /**
   * users can be grouped
   */
  val groups = Map[String, Option[Set[String]]]()

  import AdvancedRoomMessages._

  def receive = {
    case Join(name) => {
      /**
       * join request from the user
       */
      if(users contains name) {
        /**
         * existing user
         */
        val iteratee = Iteratee.ignore[String]
        sender ! ((iteratee, users(name)._1))
      }else {
        /**
         * join request from a new user
         */

        /**
         * create new broadcast channel
         */
        val (enumerator, channel) = Concurrent.broadcast[String]
        users += ((name, (enumerator, channel)))
        val iteratee = Iteratee.foreach[String](msg => {
          //do something with the message
        }).map{ _ => {
          /**
           * user closed his websocket client, so remove the user
           * warning ... also remove the corresponding user name in groups
           */
          users(name)._2.eofAndEnd()
          users -= name
        }}
        sender ! (iteratee, enumerator)
      }
    }
    case BroadcastGroup(msg, gName) => {
      groups(gName) match {
        case Some(gMates) => {
          gMates.foreach { person => users(person)._2.push(msg)}
        }
        case None => Logger.info("empty group") //ignore sending message
      }
    }
    case BroadcastAll(msg) => {
      channel push msg
    }
    case AddGroup(gName: String) => {
      groups += ((gName, None))
    }
    case RemoveGroup(gName: String) => {
      groups -= gName
    }
    case AddUserToGroup(userName, gName) => {
      groups(gName) match {
        case Some(gMates) => gMates += userName
        case None => Set(userName)
      }
    }
  }
}

该过滤器必须应用于:

def chat(group_id: String) = WebSocket.using[JsValue] { request =>

    val in = Iteratee.foreach[JsValue]{ msg=>

    public_channel.push(msg)

    }

    (in, public_enumerator &> filter(group_id))
}

简短回答

package controllers

import akka.actor.Actor
import play.api.libs.iteratee.Enumerator
import play.api.libs.iteratee.Concurrent.Channel
import play.api.libs.iteratee.Concurrent
import play.api.Logger
import play.api.libs.iteratee.Iteratee
import play.api.libs.concurrent.Execution.Implicits.defaultContext

object AdvancedRoomMessages {
  case class Join(name: String)
  case class BroadcastGroup(msg: String, gName: String)
  case class BroadcastAll(msg: String)
  case class AddGroup(gName: String)
  case class RemoveGroup(gName: String)
  case class AddUserToGroup(userName: String, gName: String)
  case class removeUserFromGroup(userName: String, gName: String)
}


class AdvancedRoom extends Actor {
  import scala.collection.mutable._


  /**
   * common channel for communication
   */


  val (enumerator, channel) = Concurrent.broadcast[String]



  /**
   * every user has his own channel
   */
  val users = Map[String, (Enumerator[String],Channel[String])]()

  /**
   * users can be grouped
   */
  val groups = Map[String, Option[Set[String]]]()

  import AdvancedRoomMessages._

  def receive = {
    case Join(name) => {
      /**
       * join request from the user
       */
      if(users contains name) {
        /**
         * existing user
         */
        val iteratee = Iteratee.ignore[String]
        sender ! ((iteratee, users(name)._1))
      }else {
        /**
         * join request from a new user
         */

        /**
         * create new broadcast channel
         */
        val (enumerator, channel) = Concurrent.broadcast[String]
        users += ((name, (enumerator, channel)))
        val iteratee = Iteratee.foreach[String](msg => {
          //do something with the message
        }).map{ _ => {
          /**
           * user closed his websocket client, so remove the user
           * warning ... also remove the corresponding user name in groups
           */
          users(name)._2.eofAndEnd()
          users -= name
        }}
        sender ! (iteratee, enumerator)
      }
    }
    case BroadcastGroup(msg, gName) => {
      groups(gName) match {
        case Some(gMates) => {
          gMates.foreach { person => users(person)._2.push(msg)}
        }
        case None => Logger.info("empty group") //ignore sending message
      }
    }
    case BroadcastAll(msg) => {
      channel push msg
    }
    case AddGroup(gName: String) => {
      groups += ((gName, None))
    }
    case RemoveGroup(gName: String) => {
      groups -= gName
    }
    case AddUserToGroup(userName, gName) => {
      groups(gName) match {
        case Some(gMates) => gMates += userName
        case None => Set(userName)
      }
    }
  }
}
为每个用户维护单独的频道,并使组与用户关联

长答案

package controllers

import akka.actor.Actor
import play.api.libs.iteratee.Enumerator
import play.api.libs.iteratee.Concurrent.Channel
import play.api.libs.iteratee.Concurrent
import play.api.Logger
import play.api.libs.iteratee.Iteratee
import play.api.libs.concurrent.Execution.Implicits.defaultContext

object AdvancedRoomMessages {
  case class Join(name: String)
  case class BroadcastGroup(msg: String, gName: String)
  case class BroadcastAll(msg: String)
  case class AddGroup(gName: String)
  case class RemoveGroup(gName: String)
  case class AddUserToGroup(userName: String, gName: String)
  case class removeUserFromGroup(userName: String, gName: String)
}


class AdvancedRoom extends Actor {
  import scala.collection.mutable._


  /**
   * common channel for communication
   */


  val (enumerator, channel) = Concurrent.broadcast[String]



  /**
   * every user has his own channel
   */
  val users = Map[String, (Enumerator[String],Channel[String])]()

  /**
   * users can be grouped
   */
  val groups = Map[String, Option[Set[String]]]()

  import AdvancedRoomMessages._

  def receive = {
    case Join(name) => {
      /**
       * join request from the user
       */
      if(users contains name) {
        /**
         * existing user
         */
        val iteratee = Iteratee.ignore[String]
        sender ! ((iteratee, users(name)._1))
      }else {
        /**
         * join request from a new user
         */

        /**
         * create new broadcast channel
         */
        val (enumerator, channel) = Concurrent.broadcast[String]
        users += ((name, (enumerator, channel)))
        val iteratee = Iteratee.foreach[String](msg => {
          //do something with the message
        }).map{ _ => {
          /**
           * user closed his websocket client, so remove the user
           * warning ... also remove the corresponding user name in groups
           */
          users(name)._2.eofAndEnd()
          users -= name
        }}
        sender ! (iteratee, enumerator)
      }
    }
    case BroadcastGroup(msg, gName) => {
      groups(gName) match {
        case Some(gMates) => {
          gMates.foreach { person => users(person)._2.push(msg)}
        }
        case None => Logger.info("empty group") //ignore sending message
      }
    }
    case BroadcastAll(msg) => {
      channel push msg
    }
    case AddGroup(gName: String) => {
      groups += ((gName, None))
    }
    case RemoveGroup(gName: String) => {
      groups -= gName
    }
    case AddUserToGroup(userName, gName) => {
      groups(gName) match {
        case Some(gMates) => gMates += userName
        case None => Set(userName)
      }
    }
  }
}

根据我的实验,
Concurrent.broadcast
并没有发送给每个人(可能是一些不幸的命名?) 以下是我所使用的与预期一样有效的方法

package controllers

import play.api._
import play.api.mvc._
import play.api.libs.iteratee.Concurrent
import play.api.libs.iteratee.Iteratee
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.collection.mutable.{Set => MS}
import scala.concurrent._ 

object Application extends Controller {
  val c:MS[(Int, Concurrent.Channel[String])] = MS() // (channelID, Channel))

  def pushHello = c.foreach(_._2.push("hello")) // push to ALL channels

  def index = WebSocket.async[String] { _ => future{
        val (out,channel) = Concurrent.broadcast[String]          
        val channelID = scala.util.Random.nextInt
        c.add((channelID, channel))
        val in = Iteratee.foreach[String] {
          _ match {
            case any => channel.push("received:"+any) // push to current channel
          }
        }.map { _ => c.retain(x => x._1 != channelID) }
        (in, out)
    }
  }
}

Concurrent.broadcast
做你想做的事吗?broadcast会将此消息发送给所有人,如果我错了,请纠正我。例如,我想将数据发送给25人中的5人。您必须对枚举数应用筛选器。如果邮件包含收件人的任何信息,这将起作用。这将向所有用户发送邮件,只向部分用户发送数据,如果我有一个10.000,我只想向100发送,单独向100发送相同的邮件,这不是最有效的方式,这就是为什么我在寻找一些广播解决方案。但是如果我有10000个用户,我向其中20个用户发送消息,然后我必须检查所有这10000条消息,这对我来说不是很优化,也许有某种方式可以组合频道或类似的东西
package controllers

import play.api._
import play.api.mvc._
import play.api.libs.iteratee.Concurrent
import play.api.libs.iteratee.Iteratee
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.collection.mutable.{Set => MS}
import scala.concurrent._ 

object Application extends Controller {
  val c:MS[(Int, Concurrent.Channel[String])] = MS() // (channelID, Channel))

  def pushHello = c.foreach(_._2.push("hello")) // push to ALL channels

  def index = WebSocket.async[String] { _ => future{
        val (out,channel) = Concurrent.broadcast[String]          
        val channelID = scala.util.Random.nextInt
        c.add((channelID, channel))
        val in = Iteratee.foreach[String] {
          _ match {
            case any => channel.push("received:"+any) // push to current channel
          }
        }.map { _ => c.retain(x => x._1 != channelID) }
        (in, out)
    }
  }
}