Ratchet Stores the connection between the user and the sent message outside the server instance.

I follow along with textbooks here and got a ratchet server job.

My chat class is the same as the textbook, now more or less, so it makes no sense to show what is not here yet, since my question is about the implementation strategy.

In the question that I attached, the user looked at how to get the connection object of a specific user. In the main answer solution, tracking resource identifiers seems to be the way to do this.

For example, when a connection is created, there is this code.

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";
    }

This creates a member variable clientsto hold all connections, and you simply reference it by identifier to send the message. However, these clients are instances. ConnectionInterface $conn

Then, to send the message, you simply use the code below by entering the client ID as the array key. Very simple.

$client = $this->clients[{{insert client id here}}];
$client->send("Message successfully sent to user.");

As we know, ratchet works as a script on the server in an event loop that never ends.

I am launching a Symfony project in which, outside the server instance, it works with ratchet code when a user performs a specific action on the system I need to send a message to a specific client connected to the server.

, , ConnectionInterface , WebSockets. ?

, .

enter image description here

:

?

+4
1

, , -, Websocket ( ).

1:

, , bin "startwebsocketserver.php" ( )

2:

.

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\Socket\Server;
use React\EventLoop\Factory;

use WebSocketApp\Websocketserver;
use WebSocketApp\Htmlserver;
use WebSocketApp\Clientevent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Ratchet\App;

require dirname(__DIR__) . '/vendor/autoload.php';
require_once dirname(__DIR__) . '/bootstrap/bootstrap.php';

$websocketserver = new Websocketserver();

$dispatcher = new EventDispatcher(); //@JA - This is used to maintain communication between the websocket and HTTP Rest API Server
$dispatcher->addListener('websocketserver.updateclient', array($websocketserver, 'updateClient'));

//// 1. Create the event loop
$loop = Factory::create();

//// 2. Create websocket servers
$webSock = new Server($loop);
new IoServer(
    new HttpServer(
        new WsServer( $websocketserver )
    ),
    $webSock
);
$webSock->listen('8080', '0.0.0.0');

$app = new App( 'localhost', 6677, '0.0.0.0',$loop );
$app->route( '/', new Htmlserver(), [ '*' ] );//@JA - Allow any origins for last parameter

$app->run();

, . - , . Doctrine 2 .

, HTTP- WebSocket . $app->route, HTTP- API WebSocket - PHP.

$loop Websocket HTTPServer.

3:

websockets. WebSocketApp. 3 .

Clientevent.php Htmlserver.php Websocketserver.php

1 1 . , Autoload PSR-0 .

, .

4:

composer.json , .

{
    "require": {
        "doctrine/orm": "^2.5",
        "slim/slim": "^3.0",
        "slim/twig-view": "^2.1",
        "components/jquery": "*",
        "components/normalize.css": "*",
        "robloach/component-installer": "*",
        "paragonie/random_compat": "^2.0",
        "twilio/sdk": "^5.5",
        "aws/aws-sdk-php": "^3.22",
        "mailgun/mailgun-php": "^2.1",
        "php-http/curl-client": "^1.7",
        "guzzlehttp/psr7": "^1.3",
        "cboden/ratchet": "^0.3.6"
    },
    "autoload": {
        "psr-4": {
            "app\\":"app",
            "Entity\\":"entities"
        },
        "psr-0": {
            "WebSocketApp":"websockets"
        },
        "files": ["lib/utilities.php","lib/security.php"]
    }
}

, - "". .

"psr-0": {
            "WebSocketApp":"websockets"
        },

websockets WebSocketApp. psr-0 , , WebSocketApp -.

5:

htmlserver.php ...

<?php
namespace WebSocketApp;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Message\Request;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServerInterface;

class Htmlserver implements HttpServerInterface {
    protected $response;

    public function onOpen( ConnectionInterface $conn, RequestInterface $request = null ) {
        global $dispatcher;

        $this->response = new Response( 200, [
            'Content-Type' => 'text/html; charset=utf-8',
        ] );

        $query = $request->getQuery();
        parse_str($query, $get_array);//@JA - Convert query to variables in an array

        $json = json_encode($get_array);//@JA - Encode to JSON

        //@JA - Send JSON for what you want to do and the token representing the user & therefore connected user as well.
        $event = new ClientEvent($json);
        $dispatcher->dispatch("websocketserver.updateclient",$event);

        $this->response->setBody('{"message":"Successfully sent message to websocket server")');
        echo "HTTP Connection Triggered\n";
        $this->close( $conn );
    }

    public function onClose( ConnectionInterface $conn ) {
        echo "HTTP Connection Ended\n";
    }

    public function onError( ConnectionInterface $conn, \Exception $e ) {
        echo "HTTP Connection Error\n";
    }

    public function onMessage( ConnectionInterface $from, $msg ) {
        echo "HTTP Connection Message\n";
    }

    protected function close( ConnectionInterface $conn ) {
        $conn->send( $this->response );
        $conn->close();
    }
}

- WebSocket HTTP, , cURL - PHP. WebSocket Symfony Event JSON. , , JSON.

6:

clientevent.php ...

<?php
namespace WebSocketApp;

use Symfony\Component\EventDispatcher\Event;

use Entity\User;
use Entity\Socket;

class Clientevent extends Event
{
    const NAME = 'clientevent';

    protected $user; //@JA - This returns type Entity\User

    public function __construct($json)
    {
        global $entityManager;

        $decoded = json_decode($json,true);
        switch($decoded["command"]){
            case "updatestatus":
                //Find out what the current 'active' & 'busy' states are for the userid given (assuming user id exists?)
                if(isset($decoded["userid"])){
                    $results = $entityManager->getRepository('Entity\User')->findBy(array('id' => $decoded["userid"]));
                    if(count($results)>0){
                        unset($this->user);//@JA - Clear the old reference
                        $this->user = $results[0]; //@JA - Store refernece to the user object
                        $entityManager->refresh($this->user); //@JA - Because result cache is used by default, this will make sure the data is new and therefore the socket objects with it
                    }
                }
                break;
        }
    }

    public function getUser()
    {
        return $this->user;
    }
}

, - , Doctrine 2.. , . - PHP .

Clientevent JSON '{"command":"updatestatus","userid":"2"}'

, .

7:

Websocketserver.php ...

<?php
namespace WebSocketApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Symfony\Component\EventDispatcher\Event;

use Entity\User;
use Entity\Authtoken;
use Entity\Socket;

class Websocketserver implements MessageComponentInterface {
    protected $clients;

    public function updateClient(Event $event)
    {
       $user = $event->getUser();//@JA - Get reference to the user the event is for.

       echo "userid=".$user->getId()."\n";
       echo "busy=".($user->getBusy()==false ? "0" : "1")."\n";
       echo "active=".($user->getActive()==false ? "0" : "1")."\n";

       $json["busy"]    = ($user->getBusy()==false ? "0" : "1");
       $json["active"]  = ($user->getActive()==false ? "0" : "1");

       $msg = json_encode($json);

       foreach($user->getSockets() as $socket){
            $connectionid = $socket->getConnectionid();
            echo "Sending For ConnectionID:".$connectionid."\n";
            if(isset($this->clients[$connectionid])){
                $client = $this->clients[$connectionid];
                $client->send($msg);
            }else{
                echo "Client is no longer connected for this Connection ID:".$connectionid."\n";
            }
       }
    }

    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) {
        global $entityManager;

        echo sprintf('Connection %d sending message "%s"' . "\n", $from->resourceId, $msg);

        //@JA - First step is to decode the message coming from the client.  Use token to identify the user (from cookie or local storage)
        //@JA - Format is JSON {token:58d8beeb0ada3:4ffbd272a1703a59ad82cddc2f592685135b09f2,message:register}
        $json = json_decode($msg,true);
        //echo 'json='.print_r($json,true)."\n";
        if($json["message"] == "register"){
            echo "Registering with server...\n";

            $parts = explode(":",$json["token"]);

            $selector = $parts[0];
            $validator = $parts[1];

            //@JA - Look up records in the database by selector.
            $tokens = $entityManager->getRepository('Entity\Authtoken')->findBy(array('selector' => $selector, 'token' => hash('sha256',$validator)));

            if(count($tokens)>0){
                $user = $tokens[0]->getUser();
                echo "User ID:".$user->getId()." Registered from given token\n";
                $socket = new Socket();
                $socket->setUser($user);
                $socket->setConnectionid($from->resourceId);
                $socket->setDatecreated(new \Datetime());

                $entityManager->persist($socket);
                $entityManager->flush();
            }else{
                echo "No user found from the given cookie token\n";
            }

        }else{
            echo "Unknown Message...\n";
        }     
    }

    public function onClose(ConnectionInterface $conn) {
        global $entityManager;

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

        //@JA - We need to clean up the database of any loose ends as well so it doesn't get full with loose data
        $socketResults = $entityManager->getRepository('Entity\Socket')->findBy(array('connectionid' => $conn->resourceId));
        if(count($socketResults)>0){
            $socket = $socketResults[0];
            $entityManager->remove($socket);
            $entityManager->flush();
            echo "Socket Entity For Connection ID:".$conn->resourceId." Removed\n";
        }else{
            echo "Was no socket info to remove from database??\n";
        }

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

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

        $conn->close();
    }
}

. , , . onOpen.

onMessage - , - . JSON. , , , , cookie, , , .

, , authToken, cookie.

Socket $from- > resourceId

, .

onClose , , , , .

, , updateClient Symfony, HtmlServer, .

, -. -, -, , , . Doctrine $user- > getSockets(), .

$client- > send ($ msg), -.

8:

, javascript - - .

var hostname = window.location.hostname; //@JA - Doing it this way will make this work on DEV and LIVE Enviroments
    var conn = new WebSocket('ws://'+hostname+':8080');
    conn.onopen = function(e) {
        console.log("Connection established!");
        //@JA - Register with the server so it associates the connection ID to the supplied token
        conn.send('{"token":"'+$.cookie("ccdraftandpermit")+'","message":"register"}');
    };

    conn.onmessage = function(e) {
        //@JA - Update in realtime the busy and active status
        console.log(e.data)
        var obj = jQuery.parseJSON(e.data);
        if(obj.busy == "0"){
            $('.status').attr("status","free");
            $('.status').html("Free");
            $(".unbusy").css("display","none");
        }else{
            $('.status').attr("status","busy");
            $('.status').html("Busy");
            $(".unbusy").css("display","inline");
        }
        if(obj.active == "0"){
            $('.startbtn').attr("status","off");
            $('.startbtn').html("Start Taking Calls");
        }else{
            $('.startbtn').attr("status","on");
            $('.startbtn').html("Stop Taking Calls");
        }
    };

JSON.

9:

- PHP, - .

function h_sendWebsocketNotificationToUser($userid){
    //Send notification out to Websocket Server
    $ch = curl_init(); 
    curl_setopt($ch, CURLOPT_URL, "http://localhost/?command=updatestatus&userid=".$userid); 
    curl_setopt($ch, CURLOPT_PORT, 6677);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
    $output = curl_exec($ch); 
    curl_close($ch); 
}

updateStatus .

10:

10, ! , ... - , Docker, . - .

docker exec -itd draftandpermit_web_1 bash -c "cd /var/www/callcenter/livesite; php bin/startwebsocketserver.php"

- . -d, , . , , . , , , .

, . - .

ports: 
            - "80:80"
            - "8080:8080"
            - "6060:80"
            - "443:443"
            - "6677:6677"
            #This is used below to test on local machines, just portforward this on your router.
            - "8082:80"

, 8080 WebSockets, .

, , .

enter image description here

+2

Source: https://habr.com/ru/post/1673560/


All Articles