Ratchet WebSocket - send message immediately - php

I have to do some complicated computing between sending messages, but first message is sent with second after computing. How I can send it immediately?
<?php
namespace AppBundle\WSServer;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class CommandManager implements MessageComponentInterface {
public function onOpen(ConnectionInterface $conn) {
//...
}
public function onClose(ConnectionInterface $connection) {
//...
}
public function onMessage(ConnectionInterface $connection, $msg) {
//...
$connection->send('{"command":"someString","data":"data"}');
//...complicated computing
sleep(10);
//send result
$connection->send('{"command":"someString","data":"data"}');
return;
}
}
Starting server:
$server = IoServer::factory(
new HttpServer(
new WsServer(
$ws_manager
)
), $port
);

send eventually makes its way to React's EventLoop which sends the message asynchronously when it's "ready". In the mean time it relinquishes execution and then the scripts executes your calculation. By the time that's done the buffer will then send your first and second messages. To avoid this you can tell the calculation to execute on the EventLoop as a tick after the current buffers are drained:
class CommandMessage implements \Ratchet\MessageComponentInterface {
private $loop;
public function __construct(\React\EventLoop\LoopInterface $loop) {
$this->loop = $loop;
}
public function onMessage(\Ratchet\ConnectionInterface $conn, $msg) {
$conn->send('{"command":"someString","data":"data"}');
$this->loop->nextTick(function() use ($conn) {
sleep(10);
$conn->send('{"command":"someString","data":"data"}');
});
}
}
$loop = \React\EventLoop\Factory::create();
$socket = new \React\Socket\Server($loop);
$socket->listen($port, '0.0.0.0');
$server = new \Ratchet\IoServer(
new HttpServer(
new WsServer(
new CommandManager($loop)
)
),
$socket,
$loop
);
$server->run();

Related

WebSocket PHP handles each message separately

I use Ratchet library to create websocket on php and I set a timeout of 10 seconds to return a message to the client, then I see that it will wait for the first message to be processed before processing the next message, how to make each message it will process always without waiting for the previous message
socket.php
<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Socket implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$time = date("H:i:s");
sleep(10);
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s"' . "\n"
, $from->resourceId, $msg);
$from->send("Server Reply: ".$time);
}
public function onClose(ConnectionInterface $conn) {
// The connection is closed, remove it, as we can no longer send it messages
$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();
}
}
run.php
<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Socket;
require 'vendor/autoload.php';
require "socket.php";
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Socket()
)
),
8765
);
$server->run();
Image

how to trigger onMessage function from server-side on ratchet websocket?

in my project , i want to send data to some clients automatically , without receiving any request from clients.but i cant access to clients from out side of MessageComponentInterface class object. i prefer to tell to MessageComponentInterface class ; send to alive clients a message. so i need to trigger onMessage function from server-side , how can i do it ?
here is my WebSocketCon class :
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class WebSocketCon implements MessageComponentInterface {
protected $clients;
public $users;
public function __construct() {
$this->clients = new \SplObjectStorage;
$this->users = [];
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg);
if($data->command=="subscribe"){
$this->users[(int)$data->uid] = $from;
echo "New subscribe! ({$data->uid})\n";
}
}
public function sendMessageToAll($msg){
foreach ($this->clients as $client) {
if ($from !== $client) {
$client->send($msg);
}
}
}
public function sendMessageTo($idusers,$msg){
foreach ($idusers as $idu) {
$idu = (int) $idu;
if(array_key_exists($idu,$this->users)){
$this->users[$idu]->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
if (($key = array_search($conn, $this->users)) !== false) {
unset($this->users[$key]);
}
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
and here is my cmd.php :
<?php
require 'vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require 'classes/WebSocketCon.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new WebSocketCon()
)
),
8081
);
$server->run();
?>

Periodically sending messages to clients in Ratchet

I'm trying to periodically send a "hello world!" message to all clients connected to the chat-server from the Ratchet tutorial
I will post all of the code here:
Chat.php:
<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface {
public $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
//this worked but I don't want this behaviour
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) {
// The connection is closed, remove it, as we can no longer send it messages
$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();
}
}
chat-server.php:
<?php
use Ratchet\Server\IoServer;
use MyApp\Chat;
require dirname(__DIR__) . '/vendor/autoload.php';
$server = IoServer::factory(
new Chat(),
8080
);
$server->run();
To test how much of the docs I understood , I added a timer to the server's loop
<?php
use Ratchet\Server\IoServer;
use MyApp\Chat;
require dirname(__DIR__) . '/vendor/autoload.php';
$server = IoServer::factory(
new Chat(),
8080
);
// My code here
$server->loop->addPeriodicTimer(5, function () {
echo "custom loop timer working !";
});
$server->run();
and it worked fine outputting that string every five seconds after starting the server.
Now I tried doing it like so, trying to send a message to clients stored in the MessageComponentInterface called Chat from the tutorial
$server->loop->addPeriodicTimer(5, function () {
foreach ($server->app->clients as $client) {
$client->send("hello client");
}
});
But I'm getting that $server->app is NULL which is probably because I'm now inside the function() block .I'm not an expert when it comes to Object oriented PHP, and this little project will sure help me a lot.
How can I access the MessageComponentInterface called app property of the server inside the timer and then send data to the clients stored in there?
$server isn't defined in the function scope and variables from the parent scope don't get inherited to the child scope by default. Closures can inherit variables from the parent scope by using the use language construct.
$server->loop->addPeriodicTimer(5, function () use ($server) {
foreach ($server->app->clients as $client) {
$client->send("hello client");
}
});
More information about anonymous functions (closures): https://secure.php.net/manual/en/functions.anonymous.php
More information about variables scope: https://secure.php.net/manual/en/language.variables.scope.php
After some updates the Client Connections are accessible in the MessageHandler
$port = 3001;
$handler = new MessageHandler();
$server = IoServer::factory(
new HttpServer(
new WsServer(
handler
)
),
$port
);
$server->loop->addPeriodicTimer(0.1, function () use ($handler) {
handler->doStuff();
});
$server->run();
The MessageHandler can be found here. The doStuff method should be implemented in this class:
https://github.com/leorojas22/symfony-websockets/blob/master/src/Websocket/MessageHandler.php

Ratchet Websocket can't receive data from client?

I have a real time web application with the frameWork Symfony. I need to send data from client to the webscket server.so I have try this :
var conn = new WebSocket('ws://127.0.0.1:8080');
console.log (conn);
conn.onopen = function (e) {
console.log ("Connection established!");
conn.send("xoxo");
};
It does't show any error and in the server side I have this :
The 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, 'EditMessage'));
$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();
and this is my App code :
class AggregateApplication implements WampServerInterface {
protected $clients;
protected $comming;
public function __construct() {
$this->clients = array();
$this->comming = array();
}
public function onOpen(ConnectionInterface $conn){
$this->clients[array_shift($this->comming)]=$conn;
echo "New connection! ".array_shift($this->comming)." ({$conn->resourceId})\n";
}
public function onCall(ConnectionInterface $conn, $id, $topic, array $params){
}
public function onSubscribe(ConnectionInterface $conn, $topic){
echo "onSubscribe";
}
public function onUnSubscribe(ConnectionInterface $conn, $topic){
}
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible){
}
public function onClose(ConnectionInterface $conn) {
unset($this->clients[array_search($conn, $this->clients)]);
echo 'close connection ';
}
public function onError(ConnectionInterface $conn, \Exception $e) {
}
}
The problem I can't find were I ll catch the message sent from client?
Your message does not conform to the WAMP standard. Take a look at AutobahnJS for your client connection. Also take a look at the Ratchet Push Integration Tutorial, which has a functional example.
Remember that the WAMP standard is only a suggestion for Push Integration(ZMQ).You can use to handle this mechanism on client side by pure javascript, without using any library such as AutobahnJS
Server code:
// Create loop for listen
$loop = React\EventLoop\Factory::create();
$pusher = new Pusher;
// Listen for the web server to make a ZeroMQ push after an ajax request
$context = new React\ZMQ\Context($loop);
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind('tcp://127.0.0.1:5555'); // Binding to 127.0.0.1 means the only client that can connect is itself
$pull->on('message', array($pusher, 'onBlogEntry'));
// Set up our WebSocket server for clients wanting real-time updates
$webSock = new React\Socket\Server($loop);
$webSock->listen(8080, '127.0.0.1'); // Binding to 127.0.0.1 means remotes can connect
$webServer = new Ratchet\Server\IoServer(
new Ratchet\WebSocket\WsServer(
$pusher
), $webSock
);
// run server for listen websocket base connection
$loop->run();
Your Application code doesn't have need to Topic class (WAMP Class) for message pattern for this reason it doesn't need to use WAMP and AutobahnJS.
App code for example:
class Pusher implements MessageComponentInterface {
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {}
public function onMessage(ConnectionInterface $from, $msg) {}
public function onClose(ConnectionInterface $conn) {}
public function onError(ConnectionInterface $conn, \Exception $e) {}
public function onBlogEntry($entry) {}
}
Watch the below slide.
http://wamp-proto.org/
The client can communictate with the server via Remote Procedure Calls (RPC).
If you use AutobahnJS (http://autobahn.ws/js/), you can add this in your client.
session.call('myaction', [mydata]).then(
function (res) {
console.log("Result:", res);
}
);
In your Pusher class, you have use the below function catch the message sent from the client.
public function onCall(ConnectionInterface $conn, $id, $topic, array $params)
{
// do what you want
}

how to get the connection object of a specific user?

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();
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.
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();
}
}

Categories