我需要向当前正在处理 Play WebSocket 消息的客户端发送专用响应,而对于其他消息,我需要向所有客户端广播响应。
在 Play 聊天示例中,传入消息会立即卸载到单个 Actor:
case Connected(enumerator) =>
// Create an Iteratee to consume the feed, assuming all requests have
// a JSON "text" node, and delegate all to the Akka Actor:
val iteratee = Iteratee.foreach[JsValue] { event =>
default ! Talk(username, (event "text").as[String])
}.map { _ =>
default ! Quit(username)
}
(iteratee,enumerator)
上面,一旦连接被批准enumerator
就会被传回,这指的是同一Actor已经创建的单个chatEnumerator
:
val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
我认为这不允许我只向单个客户端发送消息?我是否应该删除广播功能并为每个客户端创建和跟踪枚举器,然后自行迭代?或者我可以以某种方式在foreach
中获取对特定于客户端的枚举器的引用?
(我知道内置的灵活性取决于实现,但这个用例对我来说似乎很常见。就像将 WebSockets 与 Socket.IO 一起使用一样,我可以轻松地将消息发送到所有客户端、除当前请求发送者之外的所有客户端或仅单个客户端。这也是我在Play 2.1.x中想要实现的目标。
一种方法是使用 Enumerator.interleave
交错两个枚举器。
因此,您可以使用 Concurrent.broadcast
创建两对(Enumerator, Channel)
两次,一对用于广播,另一对用于专用连接,并交错。(或者可能只是将Concurrent.unicast
用于私人枚举器,但我不知道如何使用它。
这是一些适用于播放 2.3.0 的示例播放代码。
object Application extends Controller {
val (publicOut,publicChannel) = Concurrent.broadcast[String]
def chat = WebSocket.using[String]{ request =>
val (privateOut,privateChannel) = Concurrent.broadcast[String]
val in = Iteratee.foreach{
msg:String => if(msg.startsWith("@")){
publicChannel.push("Broadcasted: " + msg)
}else{
privateChannel.push("Private: " + msg)
}
}
val out = Enumerator.interleave(publicOut,privateOut)
(in, out)
}
}
向特定客户端发送消息的代码有点复杂,但概念是相同的。创建一个Actor
,每个 websocket 保存一对(Enumerator, Channel)
,并向参与者发送消息。
这似乎是对scala的黑客攻击。 WebSocket-chat应用程序的Java版本使用地图来存储每个用户名和频道。然后循环遍历它们。更改/阻止消息只是根据循环内的用户名进行分支。我也在寻找一个好的解决方案。
Map<String, WebSocket.Out<JsonNode>> members = new HashMap<String, WebSocket.Out<JsonNode>>();
完整的代码在这里。