8

I am working in a real time Symfony app using Ratchet library, in this app I need to send some data to a specific user so the logic solution was to use the SessionProvider that will attach a Symfony2 Session object to each incoming Connection object. As the documentation states I have setup a non-native session handler to store my sessions i.e. in a database via PDO. and that work fine for the moment but I need to get the Connection object of a specific user to send him some data so in other way I need to find the connection object that reference to this user and I can't find a way to do it ? her's my server code :

    $app=new AggregateApplication();
    $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($app, 'onNotification'));
    $webSock = new \React\Socket\Server($loop);
    $webSock->listen(8080, '127.0.0.1');
    $handler = $this->getContainer()->get('session.handler');
    $server=new \Ratchet\Wamp\WampServer($app);
    $server = new SessionProvider($server, $handler);
    $webServer = new \Ratchet\Server\IoServer(new \Ratchet\WebSocket\WsServer($server),$webSock);
    $loop->run();
Anas EL KORCHI
  • 2,008
  • 18
  • 25
  • But how to send a message to a specific user? How to implement this in the example of 2 clients connected to one WebSocket server, each with it's own connection object :$ConnSender,$ConnReceiver? Please to clarify to me I don't find any answer to this on ratchet tag of SO.thanks – Adib Aroui Apr 01 '15 at 13:04
  • every time when a user is connected to the websocket server I get his Id and I store his connection object into an array indexed with his Id, so when I wanna to send some data to that client I just get his connection object using his Id and I use it to send my data. – Anas EL KORCHI Apr 01 '15 at 13:50
  • I understand what you mean, but please let me add another question because it is not clear for me. When I will have my array containing two connection objects with two different IDs (Id1 of sender and Id2 of receiver), how will the WebSocket server get Id2 to send him the message? `$client = $this->clients[$Id2];$client->send("Message");` – Adib Aroui Apr 01 '15 at 13:58
  • By using symfony, you can communicate between you re symfony app and the websocket server using ZMQ so if you wan't to send a msg to client client1 $context = new ZMQContext(); $socket = $context->getSocket(ZMQ::SOCKET_PUSH, 'myPusher'); $socket->connect("tcp://localhost:5555"); $entryData="{'id':'1','data':'some data to send'}" $socket->send(json_encode($entryData)); – Anas EL KORCHI Apr 01 '15 at 14:32
  • Thank you Anas. And I am very sorry for putting more newbie questions here. If I understand you well, I should also map `user_id` to `resourceId` somewhere (maybe in a db table). am I right? indexing the array with ResourceId is not enough. I want to be able to send message to Id2 without using ZeroQM (since I need no interaction with App script, I will be using entity manager service inside my Chat class to persist messages). Should I necessarly pass by http://stackoverflow.com/questions/17558657/how-to-attach-a-symfony-session-with-the-connection-object-from-ratchet? – Adib Aroui Apr 01 '15 at 16:05
  • 1
    Definetly it's not necessary but what it's recomended is every data you send from client to server need to be done with ajax in a http request then you should use ZMQ to tell webSocketServer to send same data to another client with webSockets because it's a security issue if you handel data in you re websocket server. and even if you use the thread in the link you will pass by you re app code in the thread – Anas EL KORCHI Apr 01 '15 at 16:27
  • Aaah! thank you very much for this additionnal information. I see now. From client to server, I will send AJAX call containing `sender_id`, `receiver_id` and `message`. With ZeroQM, I send the whole stuff to Websocket server. then inside my Chat class, I retrieve the connection of `receiver_id` from `clients` array and send him the message. – Adib Aroui Apr 01 '15 at 16:36
  • 1
    Exactly that was my solution to. – Anas EL KORCHI Apr 01 '15 at 16:38
  • 1
    thank you very much sir for sharing your experience, especially regarding this part of PHP programming which really lacks simple and clear doc ( in the consideration of newbs). – Adib Aroui Apr 01 '15 at 16:44
  • Anas, please I implemented 90 percent of what we discussed, I just need your help for the remaining part http://stackoverflow.com/questions/29576033/send-a-value-from-browser-to-websocket-server-while-opening-connection. thanks again – Adib Aroui Apr 11 '15 at 09:18
  • 1
    I have posted a response hope that help. – Anas EL KORCHI Apr 13 '15 at 20:32

2 Answers2

10

I had the exact same question myself (minus Symfony) and here is what I did.

Based on the hello world tutorial, I have substituted SplObjectStorage with an array. Before presenting my modifications, I'd like to comment that if you followed through that tutorial and understood it, the only thing that prevented you from arriving at this solution yourself is probably not knowing what SplObjectStorage is.

class Chat implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = array();
    }

    public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later
        $this->clients[$conn->resourceId] = $conn;
        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        $numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

        foreach ($this->clients as $key => $client) {
            if ($from !== $client) {
                // The sender is not the receiver, send to each client connected
                $client->send($msg);
            }
        }
        // Send a message to a known resourceId (in this example the sender)
        $client = $this->clients[$from->resourceId];
        $client->send("Message successfully sent to $numRecv users.");
    }

    public function onClose(ConnectionInterface $conn) {
        // The connection is closed, remove it, as we can no longer send it messages
        unset($this->clients[$conn->resourceId]);

        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";

        $conn->close();
    }
}

Of course to make it really useful you may also want to add in a DB connection, and store/retrieve those resourceIds.

Scott Yang
  • 2,348
  • 2
  • 18
  • 21
  • Thinks I have already this solution but I am just wondering why in the official doc of Rachet they have talk that we need to attach every connection object to a symfony session using session handler... If we can do this just using the sqlObjectStorage and Ids of clients? – Anas EL KORCHI Jul 19 '13 at 14:51
  • I think they mean this: "give you read-only access to the session data from your website." – Scott Yang Jul 19 '13 at 20:43
  • 1
    What if i change the resourceIds themselves ? Here is what i am doing: whenever i establish a connection, i immediately send a message that has the actual id of that user (not his resourceId, i am talking about the id i use to fetch the user's first name, last name, etc), and then upon receiving the message, i change the user's resourceId to the id i sent with the message, and then whenever i want to send that user a message, i basically loop through the spl storage and when i find a match with his id, i send him the message. Should i be doing this? Is it safe to replace resourceIds ? – doubleOrt Nov 19 '16 at 22:00
2

this is what I did, has some improvements on the same idea.

adds 2 functions that you can call elsewhere: send_to() and multicast().

namespace mine;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ws implements MessageComponentInterface {
    protected $clients;
    protected $clientids;

    public function __construct() {
        $this->clients = new \SplObjectStorage; 
        $this->clientids = array();
    }

    public function multicast($msg) {
        foreach ($this->clients as $client) $client->send($msg);
    }

    public function send_to($to,$msg) {
        if (array_key_exists($to, $this->clientids)) $this->clientids[$to]->send($msg);
    }

    public function onOpen(ConnectionInterface $conn) {
        $socket_name = "{$conn->resourceId}@{$conn->WebSocket->request->getHeader('X-Forwarded-For')}";
        $this->clients->attach($conn,$socket_name);
        $this->clientids[$socket_name] = $conn;
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        $this->multicast($msg);
    }

    public function onClose(ConnectionInterface $conn) {
        unset($this->clientids[$this->clients[$conn]]);
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}
Smellymoo
  • 97
  • 7