php - 如何使用 symfony 棘轮向特定的 websocket 客户端发送消息?
问题描述
有一个 symfony 应用程序使用带有 Ratchet 的 php websockets ( http://socketo.me/docs/sessions )。创建一个可以将接收到的消息广播到所有连接的客户端(Web 浏览器)的 websocket 应用程序似乎很常见。但是我有一个大问题是仅向特定客户端发送消息(例如,使用 getUser() 加载用户并找到其所属的 websocket 客户端对象)。
这是我的设置:
// WebsocketServer.php
$server = IoServer::factory(
new HttpServer(
new SessionProvider(
new WsServer(
new WebSocketCommunicator()
),
new MySessionHandler())
),
$this->_websocketPort
);
$server->run();
// Handler for communication with clients
class WebSocketCommunicator implements HttpServerInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage();
}
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)
{
echo "URI: " . $conn->httpRequest->getUri()->getHost();
echo "KEY: " . $conn->Session->get('key');
// ==> I need somethink like:
$user = $conn->Session->get('user_from_session')->getId();
// Attach new client
$this->clients->attach($conn);
}
public function onMessage(ConnectionInterface $pClient, $msg)
{
$msgObj = json_decode($msg);
echo "WebSocket-Request msg: " . $msg->msg;
}
}
使用 websockets 时,没有常规会话或安全对象,我可以在其中加载登录的用户实体 (->getUser())。如上面链接中所述,可以使用 Symfony2 Session 对象或包含标头和 cookie 数据的 httpRequest 对象。有没有办法在实例中检索用户对象甚至用户 ID?
我能找到的只是这样的示例和解决方法:
在打开连接时将用户 ID 从浏览器发送到 websocket 服务器
如何使用棘轮 websockets 将数据发送到服务器 onload?
https://medium.com/@nihon_rafy/create-a-websocket-server-with-symfony-and-ratchet-973a59e2df94
现在有现代解决方案吗?或者我可以阅读的任何类型的教程或参考资料?
解决方案
我正在为 websocket 服务器开发一个 Laravel 8.0 应用程序、PHP 7.4 和 cboden/Ratchet 包。当后端触发事件时,我需要向用户/用户组发送通知,或更新 UI。
我所做的是:
1)使用 composer 安装 amphp/websocket-client 包。
2)创建了一个单独的类,以实例化一个可以连接到 websocket 服务器的对象,发送所需的消息并断开连接:
namespace App;
use Amp\Websocket\Client;
class wsClient {
public function __construct() {
//
}
// Creates a temporary connection to the WebSocket Server
// The parameter $to is the user name the server should reply to.
public function connect($msg) {
global $x;
$x = $msg;
\Amp\Loop::run(
function() {
global $x;
$connection = yield Client\connect('ws://ssa:8090');
yield $connection->send(json_encode($x));
yield $connection->close();
\Amp\Loop::stop();
}
);
}
}
3)处理程序类中的 onMessage() 事件如下所示:
/**
* @method onMessage
* @param ConnectionInterface $conn
* @param string $msg
*/
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg);
// The following line is for debugging purposes only
echo " Incoming message: " . $msg . PHP_EOL;
if (isset($data->username)) {
// Register the name of the just connected user.
if ($data->username != '') {
$this->names[$from->resourceId] = $data->username;
}
}
else {
if (isset($data->to)) {
// The "to" field contains the name of the users the message should be sent to.
if (str_contains($data->to, ',')) {
// It is a comma separated list of names.
$arrayUsers = explode(",", $data->to);
foreach($arrayUsers as $name) {
$key = array_search($name, $this->names);
if ($key !== false) {
$this->clients[$key]->send($data->message);
}
}
}
else {
// Find a single user name in the $names array to get the key.
$key = array_search($data->to, $this->names);
if ($key !== false) {
$this->clients[$key]->send($data->message);
}
else {
echo " User: " . $data->to . " not found";
}
}
}
}
echo " Connected users:\n";
foreach($this->names as $key => $data) {
echo " " . $key . '->' . $data . PHP_EOL;
}
}
如您所见,您希望 websocket 服务器将消息发送到的用户在 $msg 参数中指定为字符串 ($data->to) 以及消息本身 ($data->message) . 这两件事是 JSON 编码的,因此参数 $msg 可以被视为一个对象。
4)在客户端(布局刀片文件中的javascript)当客户端连接时,我将用户名发送到websocket服务器(就像你的第一个链接建议的那样)
var currentUser = "{{ Auth::user()->name }}";
socket = new WebSocket("ws://ssa:8090");
socket.onopen = function(e) {
console.log(currentUser + " has connected to websocket server");
socket.send(JSON.stringify({ username: currentUser }));
};
socket.onmessage = function(event) {
console.log('Data received from server: ' + event.data);
};
因此,用户名及其连接号保存在 websocket 服务器中。
5)处理程序类中的 onOpen() 方法如下所示:
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo " \n";
echo " New connection ({$conn->resourceId}) " . date('Y/m/d h:i:sa') . "\n";
}
每次客户端连接到 websocket 服务器时,它的连接号或 resourceId 都会存储在一个数组中。因此,用户名存储在一个数组 ($names) 中,而密钥存储在另一个数组 ($clients) 中。
6)最后,我可以在我的项目中的任何地方创建一个 PHP websocket 客户端的实例:
public function handle(NotificationSent $event) {
$clientSocket = new wsClient();
$clientSocket->connect(array('to'=>'Anatoly,Joachim,Caralampio', 'message'=>$event->notification->data));
}
在这种情况下,我使用的是通知事件侦听器的 handle() 方法。
好吧,这适用于任何想知道如何从 PHP websocket 服务器(AKA 回显服务器)向一个特定客户端或一组客户端发送消息的人。
推荐阅读
- weblogic - 用于 Weblogic 安全领域角色创建和添加条件的 wlst 命令
- javascript - 为什么这段代码没有按应有的方式运行。请解释
- c - 三元组问题:解决它并消除错误
- firebase - 规则 Firestore,从父级获取资源?
- flutter - 具有命名参数的构造函数设置私有变量后验证
- c++ - 调试时如何在 VS Code 的集成终端中查看 C 程序的输出
- jquery - 未捕获的类型错误:this._addClass 不是产品添加/编辑页面中的函数
- java - Android Apk 中暴露的 Google Geo location Api 密钥
- jquery - 我在哪里可以找到 getJSON 中 URL 路径的位置
- selenium - 使用 Chrome 时放大后硒元素单击不起作用