打开连接时将用户 ID 从浏览器发送到 websocket 服务器

Send user ID from browser to websocket server while opening connection

Before asking this question, I did my best by reading severel questions on SO (tagged Ratchet and dealing with similar issues but to no avail. I even asked a question which received no attention and I therefore deleted it to write another one (that hopefully is more clear).

我的最终目标是使用 Ratchet 构建一个一对一的私人聊天应用程序。一切正常,只是我无法向特定用户发送消息。

每个登录用户在访问网站的安全区域时都会连接到 websocket 服务器:

$(document).ready(function() { 

    var conn = new WebSocket('ws://localhost:8080');
        conn.onopen = function(e) {
            console.log("Connection established!");

            // Here I need to send the logged in user_id to websocket server
            // and get it in onOpen method so that I can index my array 
            // of connections with user_id instead of
            //$connection->ResourceId, I explain more below

        };

        conn.onmessage = function(e) {
            console.log(e.data);
        };

});

当用户在聊天框中写消息时,消息会通过 AJAX 发送到 Web 服务器,然后使用 ZeroMQ 推送到 Websocket。在控制器中:

// Persistence of Message(message_id, sender_id, receiver_id, message_text)
                .....

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

                $pushData = array(
                       'receiver_id' => $receiver_id,
                       'sender_id'  => $user->getId(),
                       'message'  => $message->getMessageText(),
                    );
                $socket->send(json_encode($pushData));

所以最后,我的 websocket 服务器能够使用 JSON 知道哪个是接收者的 ID。但是他怎么知道哪个是那个用户的连接呢?换句话说,我需要将 websocket 连接存储在一个由用户 ID 索引的数组中。

<?php
namespace RealTime;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Wamp\WampServerInterface;

class Pusher implements WampServerInterface, MessageComponentInterface{

    private $clients;

    public function onOpen(ConnectionInterface $conn) {

        $this->clients[$conn->resourceId] = $conn;
        // I need here to get the user_id received from browser while opening connection
    }

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

        //This is not what I need (It sends to all users in array)
        foreach ($this->clients as $key => $client) {

        $client->send($entryData['message']); 
        }
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        echo $msg; 
    }
}

和 websocket 服务器:

  <?php
        require dirname(__DIR__) . '/vendor/autoload.php';
        use RealTime\Pusher;

        $loop   = React\EventLoop\Factory::create();
        $pusher = new 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, 'onMessageEntry'));


        $webSock = new React\Socket\Server($loop);
        $webSock->listen(8080, '0.0.0.0'); 
        $webServer = new Ratchet\Server\IoServer(
            new Ratchet\Http\HttpServer(
                new Ratchet\WebSocket\WsServer(
                    new Ratchet\Wamp\WampServer(
                        $pusher
                    )
                )
            ),
            $webSock
        );
        $loop->run();

        ?>

问题:

  1. 如何在打开时从客户端发送登录的 user_id connection.I 需要在 websocket 服务器中具有值,以便我可以用它索引我的客户端数组($client[user_id]=$conn 而不是 $client[recourceId]=$conn)。我尝试了 javascript 函数 send 但我不知道从哪里接收发送的数据(即使 onMessage 也没有打印任何东西)。

  2. 为什么 onMessage 方法甚至 MessageComponentInterface 都没有执行(是因为我有 onMessageEntry 方法 + $pull->on('message', array($pusher, 'onMessageEntry')); 行代码吗?

谢谢。

这是我的发现,欢迎提出任何改进此解决方案的建议。

可以使用棘轮 SessionProvider。这将需要使用指定的 Symfony 自定义会话处理程序之一。我在下面的代码中使用 PdoSessionHandler.

<?php
    require dirname(__DIR__) . '/vendor/autoload.php';

    use YourDirectory\Pusher;
    use Symfony\Component\HttpFoundation\Session\Storage\Handler;

    use \Ratchet\Session\SessionProvider;

    $pusher = new Pusher;

    $pdo = new PDO('mysql:host=localhost;dbname=community', 'root', null);

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    //This info is related to you db
    $dbOptions = array(
        'db_table'      => 'session',
        'db_id_col'     => 'sess_id',
        'db_data_col'   => 'sess_data',
        'db_time_col'   => 'sess_time',);

        $loop   = \React\EventLoop\Factory::create();
        $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, 'onMessageEntry'));

        $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\Http\HttpServer(
                new Ratchet\WebSocket\WsServer(
                    new SessionProvider(
                        new Ratchet\Wamp\WampServer(
                            $pusher
                        ),new Handler\PdoSessionHandler($pdo,$dbOptions)
                    )
                )
            ),
            $webSock
        );

        $loop->run();
    ?>

那么我的存根class会变成:

   public function onOpen(ConnectionInterface $conn) {  
        $this->clients[$conn->Session->get('current_user_id')] = $conn;
    }

public function onMessageEntry($entry) {

            $entryData = json_decode($entry, true);
            $ReceiverConnection=$this->clients[$entryData['receiver_id']];
            $ReceiverConnection->send($entryData['message']);                  
        }

但之前,我已经将用户 ID 添加到 Web 服务器的会话中(在 returns 初始页面的控制器中)

$user = $this->getUser();
$request->getSession()->set('current_user_id', $user->getId()); 

PS:

  1. 移动到 PdoSessionHandler 可以通过实现 this (Symfony) 来完成。

  2. 2我还是答不上来,不过现在把可以放在onMessage的逻辑全部移到onMessageEntry,暂时满足需求

作为在您的 clientConnection 和他的 ID 之间建立关联的替代方法,您需要在打开与您的 websocket 服务器的连接后立即使用 websockets 发送消息,此消息将包含您的用户id 你将使用它通过你的数组中的他的 ID 来索引他的连接对象。

对于第二个问题,我知道默认的 websocket 实现不能正常工作,特别是 pubsub 协议,你需要使用一个 websocket 库,我建议使用 AutobahnJS 这是一个很好的 websocket具有许多精彩功能的图书馆。

实际上,在我最后一次尝试时,我放弃了 PHP WebSocket(使这项工作变得如此复杂)并开始将 SocketIO 与 nodeJS 一起使用,这解决了我的整个问题并且可以给我一个功能简单的聊天系统.