Ratchet: implementing websocket server without zeromq and WAMP and send push message to it by server

Created on 2 Mar 2019  路  10Comments  路  Source: ratchetphp/Ratchet

I want to implement a websocket server without zeromq and WAMP,
and send message to websocket from inside server.

first I executed _server.php_ file. When it is executed it waits for incomming connections.
then I executed _msgToWS.php_ file. When it executed, it should be connected to the _websocket server_ and send the message to it and then close. but When it is executed it waits infinitely !!

please help me to find out whats the problem ?

this is my scripts:
server.php (from here)

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

    require __DIR__ . '/libs/vendor/autoload.php';
    require __DIR__ . '/chat.php';

    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                new chat()
            )
        ),
        8080
    );

    $server->run();

chat.php (from here)

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class chat implements MessageComponentInterface {

    protected $clients;

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

    public function onOpen(ConnectionInterface $conn) {

        $this->clients->attach($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 $client) {
            if ($from !== $client) {
                // The sender is not the receiver, send to each client connected
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);

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

    public function onError(ConnectionInterface $conn, \Exception $e) {

        echo "An error has occurred: {$e->getMessage()}\n";

        $conn->close();
    }
}

msgToWS.php (from here)

require __DIR__ . '/libs/vendor/autoload.php';



    function pawlConnect($msg) {
        $loop = \React\EventLoop\Factory::create();
        $connector = new \Ratchet\Client\Connector($loop);

        $connector('ws://127.0.0.1:8080', [], ['Origin' => 'origin'])
        ->then(function(\Ratchet\Client\WebSocket $conn) use ($msg) {

            $conn->on('close', function($code = null, $reason = null) {
                echo "Connection closed ({$code} - {$reason})\n";
            });

            $conn->send($msg);
            $conn->close();

        }, function(\Exception $e) use ($loop) {
            echo "Could not connect: {$e->getMessage()}\n";
            $loop->stop();
        });

        $loop->run();
    }

    $msg = array("content" => "hi to all");
    pawlConnect(json_encode($msg));

Most helpful comment

@jupitern thanks for you support. I tried a diff php socket library [https://github.com/walkor/phpsocket.io] https://github.com/walkor/phpsocket.io. And this library makes everything super easy

here is the solution https://github.com/walkor/phpsocket.io/issues/23

So use phpsocket.io-emitter with phpsocket.io

hope this can help someone in future

All 10 comments

did you find any solution to this ? Dont want to use ZeroMQ extension and wamp server to send notifications from php server to browser.

@hadesunseenn yes solved.

get this libraries by composer:

  1. cboden/ratchet
  2. react/socket

and this is my sample code:

define("CUR_DIR", __DIR__ );

global $config;

if (checkForStop()) exit;

if (checkPidRunning(@$config['pid'])) exit;

$config = [
    'pid'=>getmypid(),
    'startTime'=>time(),
    'stop'=>0,
];

file_put_contents( CUR_DIR .'/config.txt',json_encode($config));

//---------------------------------------------------------------------------

    global $clients;
    $clients = [];

    require_once CUR_DIR ."libs/vendor/autoload.php";

    class arzWebSocketClients implements Ratchet\MessageComponentInterface {

        public function onOpen(Ratchet\ConnectionInterface $conn) {

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

        }

        public function onMessage(Ratchet\ConnectionInterface $from, $msg) {
                        global $clients;

            $msg = json_decode($msg,true);
            if ($msg){

                // msg client: $clients[$from->resourceId]
                /*
                process imcomming msg and then send response to client:

                $responseToClient = [
                    "test"=>1234,
                    "test2"=>"abcd"
                ];

                $clients[$from->resourceId]->send( json_encode($responseToClient) );
                */
            }
        }

        public function onClose(Ratchet\ConnectionInterface $conn) {
                        global $clients;
            unset($clients[$conn->resourceId]);

        }

        public function onError(Ratchet\ConnectionInterface $conn, \Exception $e) {
                       global $clients;

            $conn->close();
            unset($clients[$conn->resourceId]);

        }

    }

    //-----------------------------------------------------------------

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

    //----------- every 5 seconds check for stop order
    $loop->addPeriodicTimer(5, function ($timer) use ($loop) {


        if (checkForStop()) {

            global $config;

            $time = time();

            $config['endTime']=$time;
            file_put_contents( CUR_DIR .'/config.txt',json_encode($config));

            $loop->stop();

        }

    });


    $port = 9000;
    $host = 'yourHostName.com';
    //------------- if wnat WSS use this:
    $app = new \Ratchet\Http\HttpServer(
        new \Ratchet\WebSocket\WsServer(
            new arzWebSocketClients()
        )
    );

    $secure_websockets = new \React\Socket\Server('0.0.0.0:'.$port, $loop);
    $secure_websockets = new \React\Socket\SecureServer($secure_websockets, $loop, [
        'local_cert' => CUR_DIR ."/public.pem", // .crt
        'local_pk' => CUR_DIR .'/private.pem', // .key
        'verify_peer' => false
    ]);

    $secure_websockets_server = new \Ratchet\Server\IoServer($app, $secure_websockets, $loop);
    $secure_websockets_server->run();

    /*
    //------------- if want WS use this:
    $app = new Ratchet\App($host, $port,'0.0.0.0',$loop);
        $app->route('/', new arzWebSocketClients,array('*'));
        $app->run();
    */



function checkForStop(){
    global $config;

    $a = trim(@file_get_contents(CUR_DIR ."/config.txt"));
    $config = json_decode($a,true);

    if (@$config['stop']) {
        return true;
    }

    return false;
}

function checkPidRunning($pid){

    if ($pid && @posix_getpgid($pid)) {
        return true;
    }

    return false;
}

@se8820726 thanks for quick response.

http://socketo.me/docs/hello-world
this works fine when users connected to socket send message to each other
now problem is sometimes I need to send notification only from server and it has nothing to do with the user event.
Then I tried this
http://socketo.me/docs/push
so now I can send push notifications from server but now problem is I want to use both
http://socketo.me/docs/hello-world
and
http://socketo.me/docs/push
together
The result I need is this:-
Users are doing chat with each other, and when admin adds new post to db , I need to send notification to all users connected to socket saying like admin added a new post.

But I am not able to use PUSH and other chat logic together. separately they work fine

@hadesunseenn for your problem:

I need to send notification only from server and it has nothing to do with the user event.

you can use addPeriodicTimer _method of_ $loop

//------- this function executes every 2 seconds
$loop->addPeriodicTimer(2, function ($timer) use ($loop) {

    /*
    check if there is any notification then send it to all clients

    example: 

    if ($notificationMsg){

        global $clients;

        foreach ($clients as $client){

            $client->send(  $notificationMsg );

        }


    }

    */

}

appreciate your efforts but this is not a solution. What we need to do is somehow call
public function onMessage(ConnectionInterface $from, $msg) {}
of class
MessageComponentInterface
from server.

What we need to do is somehow call
public function onMessage(ConnectionInterface $from, $msg) {}
of class
MessageComponentInterface
from server

another way is implementing a websocket connection in server, to your websocket server and when there was any notification send it to your webscoket server from websocket connection and then send that notification from websocket server to all clients:

websocket connection to websocket server:

require_once "libs/vendor/autoload.php";

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

$reactConnector = new \React\Socket\Connector($loop, [
    'tls' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    'allow_self_signed' => true
    ),
]);


$connector = new \Ratchet\Client\Connector($loop, $reactConnector);

function connectToAnotherWS() use ($connector){

    $connector('wss://yourWbSocketUrl')
    ->then(function(\Ratchet\Client\WebSocket $conn) use ($loop)  {

        global $con;
        $con = $conn;


        $conn->on('close', function($code = null, $reason = null) use ($conn,$loop) {

            global $con;
            $con = null;

            sleep(5);
            connectToAnotherWS();
        });

    }, function(\Exception $e) use ($loop) {

        global $con;
        $con = null;

        $loop->stop();
    });

}

connectToAnotherWS();

$loop->addPeriodicTimer(2, function ($timer) use ($loop) {


    //check if there is any notification send it to websocket server

    global $con;
    if ($notif && $con){

        $msg = ['fromAdmin'=>$notif];

        $con->send( json_encode($msg) );

    }

});

$loop->run();

and onMessage method on websocket server:

public function onMessage(Ratchet\ConnectionInterface $from, $msg) {

    global $clients;

    $msgDecoded = json_decode($msg);
    if (@$msgDecoded->fromAdmin){

        $notif = $msgDecoded->fromAdmin;
        $mustSendMsg = json_encode($notif);

        foreach ($clients as $id=>$client){

            if ($id != $from->resourceId){

                $client->send( $mustSendMsg )

            }
        }
    }
}

On your file msgToWs.php why do you implement a loop if you want your connection to close after message is sent? A loop means it will keep connected and wait for another trigger to do something.
If you only want to send one message and close you should do :

````
require __DIR__ . '/vendor/autoload.php';

\Ratchet\Client\connect('ws://127.0.0.1:8080')->then(function($conn) {
    $conn->send('Hello World!');
}, function ($e) {
    echo "Could not connect: {$e->getMessage()}\n";
});

````

of course after this your script msgToWs.php will end. I don't quite understand your pourpose. If you want a scalable solution you should use a zeroMQ or redis loop for example.

@jupitern so what you suggest ? I dont want to use class WampServerInterface to send push notifcations from server. Can I implement it using only class MessageComponentInterface ? I checked the ZeroMQ example here http://socketo.me/docs/push but they use class WampServerInterface.

I am using ratchet and redis pubsub with the package bellow
https://github.com/clue/reactphp-redis#createlazyclient

I have a loop registered to a redis pub sub channel and anytime I want to send a message to the server that then gets redirected to the client I just push it in that redis pub sub list.

with the solutions bellow I can have multiple ratchet servers running distributing the client to them and they all register one or more redis pub sub channels

Pseudo code:
````
// this is my class that implements MessageConponentInterface
$chatServer = new \Lib\Chat\ChatServer($serverIp, $port);

// Start the chat server
$loop = EventLoopFactory::create();

// set redis PubSub loop
$this->registerRedisPubSubLoop($loop, $chatServer);

$socket = new Server("{$bindIp}:{$port}", $loop);

$httpServer = new HttpServer($wsServer);
$server = new IoServer($httpServer, $socket, $loop);
$server->run();

private function registerRedisPubSubLoop($loop, \Lib\Chat\ChatServer $chatServer)
{
// redis server settings
$settings = ['host' => '', 'port' => '', 'password' => '', 'database' => ''];

    $factory = new Factory($loop);

    $client = $factory->createLazyClient(
        'redis://'.$settings['host'].':'.$settings['port'].'?password='.$settings['password'].'&db='.$settings['database']
    );

    $client->subscribe($this->pubSubChannelName)->then(function () {
        // connected!!!
    }, function (\Exception $e) use ($client) {
        // error!!!
        $client->close();
    });

    $client->on('message', function ($channel, $message) use($chatServer) {
        $message = (object)unserialize(gzuncompress($message));

        // send the message to the clients you want or all.
        // your $chatServer class should have a method to do this
    });
}

````

@jupitern thanks for you support. I tried a diff php socket library [https://github.com/walkor/phpsocket.io] https://github.com/walkor/phpsocket.io. And this library makes everything super easy

here is the solution https://github.com/walkor/phpsocket.io/issues/23

So use phpsocket.io-emitter with phpsocket.io

hope this can help someone in future

Was this page helpful?
0 / 5 - 0 ratings

Related issues

grappetite-ali picture grappetite-ali  路  5Comments

piotrchludzinski picture piotrchludzinski  路  5Comments

mhlz picture mhlz  路  9Comments

Hemant3105 picture Hemant3105  路  6Comments

ThePatzen picture ThePatzen  路  3Comments