Ratchet PHP WAMP - React / ZeroMQ - 特定用户广播

2023-12-21

Note: 这是not这个问题 https://stackoverflow.com/questions/17583903/how-to-get-the-connection-object-of-a-specific-user它利用MessageComponentInterface。我在用WampServerInterface相反,所以这个问题具体涉及该部分。我需要带有代码示例和解释的答案,因为我认为这对将来的其他人有帮助。

尝试对单个用户进行循环推送

我正在使用 Ratchet 和 ZeroMQ 的 WAMP 部分,目前我有一个工作版本推送集成教程 http://socketo.me/docs/push.

我正在尝试执行以下操作:

  • Zeromq 服务器已启动并正在运行,准备记录订阅者和取消订阅者
  • 用户通过 websocket 协议在浏览器中连接
  • A loop开始发送数据到特定用户谁要求的
  • 当用户断开连接时,该用户数据的循环将停止

我有第 (1) 点和第 (2) 点工作,但我遇到的问题是第三点:

首先:如何仅向每个特定用户发送数据?广播将其发送给每个人,除非“主题”最终可能是个人用户 ID?

第二:我有一个很大的安全问题。如果我要从客户端发送哪个用户 ID 想要订阅(这似乎是我需要的),那么用户只需将变量更改为另一个用户的 ID,然后就会返回他们的数据。

第三:我必须运行一个单独的 php 脚本包含用于启动实际循环的 Zeromq 代码。我不确定这是最好的方法,我宁愿让它完全在代码库中工作,而不是单独的 php 文件。这是我需要整理的一个主要领域。

以下代码显示了我目前拥有的内容。

仅从控制台运行的服务器

我逐字输入php bin/push-server.php运行这个。订阅和取消订阅都会输出到此终端以用于调试目的。

$loop   = React\EventLoop\Factory::create();
$pusher = Pusher;

$context = new React\ZMQ\Context($loop);
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind('tcp://127.0.0.1:5555');
$pull->on('message', array($pusher, 'onMessage'));

$webSock = new React\Socket\Server($loop);
$webSock->listen(8080, '0.0.0.0'); // Binding to 0.0.0.0 means remotes can connect
$webServer = new Ratchet\Server\IoServer(
    new Ratchet\WebSocket\WsServer(
        new Ratchet\Wamp\WampServer(
            $pusher
        )
    ),
    $webSock
);

$loop->run();

通过 websocket 发送数据的 Pusher

我省略了无用的东西并专注于onMessage() and onSubscribe()方法。

public function onSubscribe(ConnectionInterface $conn, $topic) 
{
    $subject = $topic->getId();
    $ip = $conn->remoteAddress;

    if (!array_key_exists($subject, $this->subscribedTopics)) 
    {
        $this->subscribedTopics[$subject] = $topic;
    }

    $this->clients[] = $conn->resourceId;

    echo sprintf("New Connection: %s" . PHP_EOL, $conn->remoteAddress);
}

public function onMessage($entry) {
    $entryData = json_decode($entry, true);

    var_dump($entryData);

    if (!array_key_exists($entryData['topic'], $this->subscribedTopics)) {
        return;
    }

    $topic = $this->subscribedTopics[$entryData['topic']];

    // This sends out everything to multiple users, not what I want!!
    // I can't send() to individual connections from here I don't think :S
    $topic->broadcast($entryData);
}

开始循环使用上述 Pusher 代码的脚本

这是我的问题 - 这是一个单独的 php 文件,希望将来可以集成到其他代码中,但目前我不确定如何正确使用它。我是否从会话中获取用户的 ID?我仍然需要从客户端发送它......

// Thought sessions might work here but they don't work for subscription
session_start();
$userId = $_SESSION['userId'];

$loop   = React\EventLoop\Factory::create();

$context = new ZMQContext();
$socket = $context->getSocket(ZMQ::SOCKET_PUSH, 'my pusher');
$socket->connect("tcp://localhost:5555");

$i = 0;
$loop->addPeriodicTimer(4, function() use ($socket, $loop, $userId, &$i) {

   $entryData = array(
       'topic'     => 'subscriptionTopicHere',
       'userId'    => $userId
    );
    $i++;

    // So it doesn't go on infinitely if run from browser
    if ($i >= 3)
    {
        $loop->stop();
    }

    // Send stuff to the queue
    $socket->send(json_encode($entryData));
});

最后是客户端js订阅

$(document).ready(function() { 

    var conn = new ab.Session(
        'ws://localhost:8080' 
      , function() {            
            conn.subscribe('topicHere', function(topic, data) {
                console.log(topic);
                console.log(data);
            });
        }
      , function() {          
            console.warn('WebSocket connection closed');
        }
      , {                       
            'skipSubprotocolCheck': true
        }
    );
});

结论

上面的内容有效,但我确实需要弄清楚以下内容:

  • 如何向个人用户发送个人消息?当他们访问在 JS 中启动 websocket 连接的页面时,我是否也应该启动将内容推送到 PHP 队列中的脚本(zeromq)?这就是我目前手动做的事情,它只是感觉不对劲.

  • 当从 JS 订阅用户时,从会话中获取用户 ID 并从客户端发送它是不安全的。这可能是伪造的。请告诉我有一个更简单的方法,如果有的话,如何实现?


Note:我的回答在这里does not包括对 ZeroMQ 的引用,因为我不再使用它了。但是,我确信如果需要,您将能够通过此答案了解如何使用 ZeroMQ。

Use JSON

首先也是最重要的,Websocket RFC https://www.rfc-editor.org/rfc/rfc6455 and WAMP规格 http://wamp.ws/spec/声明要订阅的主题必须是string。我在这里有点作弊,但我仍然遵守规范:我改为传递 JSON。

{
    "topic": "subject here",
    "userId": "1",
    "token": "dsah9273bui3f92h3r83f82h3"
}

JSON 仍然是一个字符串,但它允许我传递更多数据来代替“主题”,并且 PHP 可以很简单地执行json_decode()在另一端。当然,您应该验证您是否确实收到了 JSON,但这取决于您的实现。

那么我在这里经历了什么,为什么?

  • Topic

主题是用户订阅的主题。您可以使用它来决定将哪些数据传递回用户。

  • UserId

显然是用户的ID。你must使用下一部分验证该用户是否存在并且允许订阅:

  • Token

这应该是一个one use随机生成的令牌,在 PHP 中生成,并传递给 JavaScript 变量。当我说“一次使用”时,我的意思是每次重新加载页面时(并且,通过扩展,在每个 HTTP 请求中),您的 JavaScript 变量应该有一个新的令牌。该令牌应根据用户 ID 存储在数据库中。

然后,一旦发出 websocket 请求,您就可以将令牌和用户 ID 与数据库中的内容相匹配,以确保用户确实是他们所说的人,并且他们没有乱搞 JS 变量。

注意:在您的事件处理程序中,您可以使用$conn->remoteAddress获取连接的 IP,因此如果有人尝试恶意连接,您可以阻止他们(记录他们或其他内容)。

为什么这有效?

它之所以有效,是因为每次有新的连接建立时,唯一的令牌确保任何用户都无法访问其他人的订阅数据。

服务器

这是我用于运行循环和事件处理程序的内容。我正在创建循环,执行所有装饰器样式对象创建,并传入我的 EventHandler(我很快就会介绍)以及循环。

$loop = Factory::create();

new IoServer(
    new WsServer(
        new WampServer(
            new EventHandler($loop) // This is my class. Pass in the loop!
        )
    ),
    $webSock
);

$loop->run();

事件处理程序

class EventHandler implements WampServerInterface, MessageComponentInterface
{
    /**
     * @var \React\EventLoop\LoopInterface
     */
    private $loop;

    /**
     * @var array List of connected clients
     */
    private $clients;

    /**
     * Pass in the react event loop here
     */
    public function __construct(LoopInterface $loop)
    {
        $this->loop = $loop;
    }

    /**
     * A user connects, we store the connection by the unique resource id
     */
    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients[$conn->resourceId]['conn'] = $conn;
    }

    /**
     * A user subscribes. The JSON is in $subscription->getId()
     */
    public function onSubscribe(ConnectionInterface $conn, $subscription)
    {
        // This is the JSON passed in from your JavaScript
        // Obviously you need to validate it's JSON and expected data etc...
        $data = json_decode(subscription->getId());
        
        // Validate the users id and token together against the db values
        
        // Now, let's subscribe this user only
        // 5 = the interval, in seconds
        $timer = $this->loop->addPeriodicTimer(5, function() use ($subscription) {
            $data = "whatever data you want to broadcast";
            return $subscription->broadcast(json_encode($data));
        });

        // Store the timer against that user's connection resource Id
        $this->clients[$conn->resourceId]['timer'] = $timer;
    }

    public function onClose(ConnectionInterface $conn)
    {
        // There might be a connection without a timer
        // So make sure there is one before trying to cancel it!
        if (isset($this->clients[$conn->resourceId]['timer']))
        {
            if ($this->clients[$conn->resourceId]['timer'] instanceof TimerInterface)
            {
                $this->loop->cancelTimer($this->clients[$conn->resourceId]['timer']);
            }
        }
    
        unset($this->clients[$conn->resourceId]);
    }

    /** Implement all the extra methods the interfaces say that you must use **/
}

基本上就是这样。这里的要点是:

  • 唯一令牌、用户 ID 和连接 ID 提供了确保一个用户无法看到另一用户的数据所需的唯一组合。
  • 唯一令牌意味着,如果同一用户打开另一个页面并请求订阅,他们将拥有自己的连接 ID + 令牌组合,因此同一用户不会在同一页面上拥有双倍的订阅(基本上,每个连接都有自己的连接)个人数据)。

扩大

在对数据进行任何操作之前,您应该确保所有数据都经过验证,而不是黑客尝试。使用类似的方法记录所有连接尝试Monolog https://github.com/Seldaek/monolog,并在发生任何严重情况时设置电子邮件转发(例如由于有人是混蛋并试图破解您的服务器而导致服务器停止工作)。

结束点

  • 验证一切。我怎么强调都不为过。您的独特令牌会根据每个请求而变化重要的.
  • 请记住,如果您在每个 HTTP 请求上重新生成令牌,并且在尝试通过 Websocket 连接之前发出 POST 请求,则必须在尝试连接之前将重新生成的令牌传回 JavaScript(否则您的令牌将无效)。
  • 记录一切。记录每个连接、询问主题和断开连接的人。 Monolog 对此非常有用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Ratchet PHP WAMP - React / ZeroMQ - 特定用户广播 的相关文章

  • 合并 url 中的 2 个输入值

    我有这样的形式
  • 使用来自另一个数据库的选择查询更新 mysql 表

    我有两个数据库 我想用另一个数据库表中的值更新一个表 我正在使用以下查询 但它不起作用 UPDATE database1 table1 SET field2 database2 table1 field2 WHERE database1 t
  • PHP 正则表达式匹配字符串的最后一次出现

    我的字符串是 text1 A373R12345 我想找到该字符串最后出现的非数字数字 所以我使用这个正则表达式 0 9 然后我得到这个结果 1 A373 2 12345 但我的预期结果是 1 A373R 它有 R 2 12345 另一个例子
  • 如何在 CakePHP 中“验证”人名?

    我有一个 PHP 脚本 应该检查 有效 的人名 但最近破解了带有空格的名称 因此我们向验证器添加了空格 除了这样做之外 有没有办法向 CakePHP 的验证器添加黑名单以阻止所有 无效 字符 而不是允许 有效 字符 注意 我 通常 知道如何
  • file_get_contents 大文件上传

    我正在尝试使用 fsockopen 上传 2GB 以上的大文件 但 file get content 出现以下错误 我无法在内存中存储大文件 我需要分块发送数据 但不知道如何执行此操作 请问有人可以指导我吗 致命错误 允许的内存大小 134
  • 自定义帖子类型的 WordPress 自定义字段

    过去有几个人出现过这个问题 但他们的问题的解决方案对我来说不起作用 我已经尝试了很多 在 WordPress 中 我创建了 3 种自定义帖子类型 1 代表 视频 新闻 和 音乐 每个内容都发布到自己的页面 我想添加自定义字段 这样我就可以为
  • 纯基于网络的版本控制系统

    我的托管服务当前不允许在其服务器上运行 允许 svn git cvs 我真的希望能够将我的开发计算机上的当前源代码与我的生产服务器 同步 我正在寻找一个纯php python ruby版本控制系统 不只是一个client对于版本控制系统 不
  • 从 php 到 JavaScript 的数组

    我正在尝试使用 json 将数组列表从 php 传输到 javascript 但它不起作用 JS ajax url getProfilePhotos php type post post or get method data if you
  • 从 php 执行 bash 脚本并立即输出回网页

    我有一组 bash 和 Perl 脚本 开发在 Linux Box 上部署所需的目录结构 可选 从svn导出代码 从这个源构建一个包 这在终端上运行良好 现在 我的客户请求此流程的 Web 界面 例如 某些页面上的 创建新包 按钮将一一调用
  • 如何在php中使用一张图像绘制形状

    我需要使用图像的一部分来创建帧图像 例如 用户将从后端上传图像片段 现在我需要根据前端用户的要求在前端创建一个框架 用户将选择框架的高度和宽度 然后他将选择该图像片段 如下所示 我没有办法做到这一点 我尝试通过 css 和 html can
  • 如何在同一 PHP 页面上多次使用 mysqli fetch_assoc() 和准备好的语句?

    有没有办法启用fetch assoc 在同一页上多次使用准备好的语句 data conn gt prepare SELECT FROM some table WHERE id data gt bind param i id data gt
  • PHP 脚本不断执行 mmap/munmap

    我的 PHP 脚本包含一个循环 它只不过是回显和取消引用指针 如 tab othertab i gt 中的内容 直到昨天 这个脚本开始变得非常慢 比以前慢了 50 倍 之前 它一直运行良好 使用 strace 后 我发现 90 的情况下 脚
  • 使用 yum 和 pear 安装 php-soap 均失败

    我正在尝试在 Centos 6 4 服务器上安装 PHP 的 SOAP 扩展 我对包管理器 从 CLI 安装包并在 PHP 中配置它们相当不熟悉 我相当有能力管理 php ini 和其他 PHP 配置文件 soap ini 等 我尝试使用以
  • PHP Intl 扩展线程安全吗?

    我一直在阅读有关 PHP 中的语言环境的内容 看起来setlocale 线程有问题 我对线程不太熟悉 文档提到它不是线程安全的 我想让我的项目能够处理某些数字格式 并且 Intl 扩展似乎很有趣 http php net manual en
  • php date_parse("2010 年 2 月") 给出日期 == 1

    当没有日期时 我将其称为 date parse 中的错误 d date parse Feb 2010 会给 d day 1 请参阅对此的评论date parse 手册页 http php net manual en function dat
  • PHP 中的encodeURI() ?

    PHP 中是否有一些不编码的encodeURI 函数 我现在用这个 function encodeURI url http php net manual en function rawurlencode php https develope
  • 通过身份验证保护 CodeIgniter 2 应用程序的正确方法是什么?

    I have Ion Auth http benedmunds com ion auth 正确安装并在我的服务器上运行 我也有默认的代码点火器2 新闻 教程在同一个 CI 安装中工作 我只是在玩 并对使用身份验证系统 封闭 或保护整个应用程
  • 点击 %40 变为 %2540

    当单击包含 符号的链接时 该网址给我 40 这就是我想要的 但是一旦我点击它 一秒钟后它就在我点击后变成了 2540 单击是在电子邮件内 然后定向到网站 其中 40 更改为 2540 我怎样才能让它停止变化 它现在得到这样的参数 email
  • 禁用 WooCommerce 手动/编辑订单的电子邮件通知

    需要 WooCommerce 专业知识 我需要禁用手动创建的订单的电子邮件通知 我必须使用处理状态 由于处理订单状态的自定义挂钩 我无法创建自定义状态 理想情况下 手动订单页面中可以勾选一个复选框 勾选后 它将禁止在每种状态下向客户发送电子
  • 通过 Sparkpost 发送 iCal 邀请

    我正在尝试使用 SparkPost 通过电子邮件以附件形式发送日历邀请 但收到电子邮件后邀请不会打开 我使用两个文件 calendarinvite php 来创建邀请 使用 Sendemail php 来发送电子邮件 calendarinv

随机推荐