I wanted to develop a chat application for my website so on learning I found that websocket is the best solution for real time communication and hence I decided to use Ratchet.
I am able to set it up and create a basic chat application.Here is the Chat class.
class Chat implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
}
public function onMessage(ConnectionInterface $from, $msg)
{
foreach ($this->clients as $client) {
if($client !== $from)
$client->send($msg);
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "The following error occured :".$e->getMessage();
$conn->close();
}
}
But I have following questions before I proceed (or can proceed) further:
Can I use Ratchet for private communication between two users?
How does Ratchet scale? I mean if I have a website with 1 million users and I am allowing anyone to chat with anyone, than is it possible? Or I am restricted to create limited public chat rooms (like SO) where anyone can
join?
Related
I want to create a real time chat app like whatsapp for android using php and ratchet websocket. Every tutorial shows how to use it with group chat (as below code) but I want to use it for private messages either. Is there any way to do this for private chat? If yes, how can I do?
<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface {
private $clients;
private $users;
public function __construct()
{
$this->clients = array();
$this->users = array();
}
public function onOpen(ConnectionInterface $conn) {
$this->clients[] = $conn;
echo "New Connection";
}
public function onMessage(ConnectionInterface $from, $msg) {
foreach ($this->clients as $client){
if ($client != $from){
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn) {
echo "Connection closed";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
}
}
I'm trying the use the already made event loop by Ratchet.
$server=IoServer::factory(new HttpServer(new WsServer(new class implements MessageComponentInterface{
public function __construct(){
// get the Ratchet Loop
}
public function onOpen(ConnectionInterface $conn){}
public function onMessage(ConnectionInterface $from, $msg){}
public function onClose(ConnectionInterface $conn){}
public function onError(ConnectionInterface $conn, \Exception $e){}
})),123);
I can get it by calling the $server->loop,
but I can't pass it to the class constructor since $server is not accessible during the initialization of itself, I wondered if there is a better way of getting it?
You can instantiation a loop and pass it to __construct
$loop = React\EventLoop\Factory::create();
$webSock = new React\Socket\Server('127.0.0.1:8080', $loop);
$server = new IoServer(new HttpServer(new WsServer(new class($loop) implements MessageComponentInterface {
protected $loop;
public function __construct(\React\EventLoop\LoopInterface $loop)
{
$this->loop = $loop;
}
public function onOpen(ConnectionInterface $conn)
{
}
public function onMessage(ConnectionInterface $from, $msg)
{
}
public function onClose(ConnectionInterface $conn)
{
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
}
})), $webSock);
$loop->run();
In this approach can't use IoServer::factory. Because you need to pass your own loop to $webSock and IoServer
Code im using:
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
}
When a client disconnects the Onclose function is triggered, is there a way I can put the client resource Id in a variable and interact with it. If not, how would I tell which client disconnected in realtime.
I figured it out
public function onClose(ConnectionInterface $conn) {
$a = array();
$b = array();
//Gets all the client Ids
foreach($this->clients as $client){
array_push($a,$client->resourceId);
}
//Deletes the disconnected client
$this->clients->detach($conn);
//Gets all the new client Ids
foreach($this->clients as $client){
array_push($b,$client->resourceId);
}
//array is made that includes the disconnceted client id by comparing both arrays made earlier
$closedClientArray = array_diff($a,$b);
//Client id is extracted from array and put in variable as a string
$closeClientString = $closedClientArray[array_keys($closedClientArray)[0]];
}
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
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();
}
}