Ratchet: How to run socketo.me chat server on a remote VPS

Created on 18 Feb 2020  路  12Comments  路  Source: ratchetphp/Ratchet

I am running a Ratchet WebSocket for PHP (http://socketo.me/). It works fine at my localhost (WAMP), I have to run it via command line php chat-server.php and it works well. I have uploaded the website on a cloud based VPS, now I have to log into the VPS with the help of Putty on my laptop and then I run the same command again it works fine, as it starts listening to a opened port and sends out responses to the clients connected with that port. My issue is, as soon my laptop is offline the chat-server.php does not work anymore, it does not listen to the port and neither does it give out responses. My question is what could be a descent way to run chat-server.php so that even when the putty terminal (laptop) is offline it may still work. Any help is highly appreciated.

All 12 comments

You need a daemon to keep the program running.

I do this using systemd. I found the thread at https://serverfault.com/questions/730239/start-n-processes-with-one-systemd-service-file useful in understanding how to set up a daemon.

So, you might do something like this:

Assuming your chat server is at /var/www/vhosts/domain.com/bin/chat-server.php

Create the file /etc/systemd/system/chat-server.service

[Unit]
Description=Chat Server

[Service]
ExecStart=/usr/bin/php /var/www/vhosts/domain.com/bin/chat-server.php
StandardOutput=null
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable the service:
sudo systemctl enable chat-server.service

Reload the daemon:
sudo systemctl daemon-reload

You can then use (start|restart|stop|status) like this:
sudo systemctl start chat-server.service

It is important that all paths in your code are absolute. To test this, try to run your code manually from a different directory on your server the way you have been:
ie: user@dev:/var/www# php /var/www/vhosts/domain.com/bin/chat-server.php

If the code still works correctly, then you're golden!

Finally, If you find that websocket disconnects due to inactivity after a while, you might want to use $loop->addPeriodicTimer() to add a heartbeat of sorts.

Hope it helps. Good luck!

Dear @michaelphipps
I am so happy to have your detailed response. I will work on it and post my feedback. Thank you very much for your help.

Dear @michaelphipps
Great, it worked for me, thank you very much. One quick and a basic question- my code is

use RatchetServerIoServer;
use RatchetHttpHttpServer;
use RatchetWebSocketWsServer;
use MyAppChat;

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

$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8888
);

$server->run();

Where should I put
$loop->addPeriodicTimer()

Thank you very much

My apologies! My implementation is more like the example at http://socketo.me/docs/push which uses the event loop (except I use RabbitMQ, which I think is way easier to implement than ZeroMQ).

You're not using the event loop, so ignore my comment about addPeriodicTimer() for now.

Thank you very much indeed, the server is running without any issue:))
Now I am working to make it secure, I am trying
"Is there a real working example of using secure TLS ratchet websocket server? #609"
I have already installed letsencrypt certificate.
Thank you very much once again.

Great!
At the risk of getting off topic, I have a working websocket server using TLS in production, and the code here https://github.com/ratchetphp/Ratchet/issues/#609#issuecomment-423882162 was instrumental in helping me to get it working.

The following code is untested, but might give you what you need.

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

// Set up our WebSocket server for clients wanting real-time updates
$webSock = new React\Socket\Server('0.0.0.0:8443', $loop);
$webSock = new React\Socket\SecureServer($webSock, $loop, [
    'local_cert' => '/etc/letsencrypt/live/domain.com/fullchain.pem',
    'local_pk' => '/etc/letsencrypt/live/domain.com/privkey.pem',
    'allow_self_signed' => FALSE,
    'verify_peer' => FALSE
]);

$webServer = new Ratchet\Server\IoServer(
    new Ratchet\Http\HttpServer(
        new Ratchet\WebSocket\WsServer(
                new MyApp\Chat
        )
    ),
    $webSock
);

$loop->run();

You _should_ then be able to access this server with wss://domain.com:8443

Keep in mind when you test this from the comand line you need to use
sudo php /var/www/wherever/secure-chat.php
so that the script has permission to access the certificate correctly.

Great, thank you very much. I was working on the same lines but thought to use cert.pem instead of fullchain.pem. I am positive your code will save me hours of mining :) Actually sometimes I get a little confused that when to use cert.pem or chain.pem or fullchain.pem.
My profound regards

Thank you so much it mostly worked for me I used the following modified code.
use RatchetServerIoServer;
use RatchetHttpHttpServer;
use RatchetWebSocketWsServer;
use MyAppChat;

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

$loop = ReactEventLoopFactory::create();
$webSock = new ReactSocketServer('0.0.0.0:8088', $loop);
$webSock = new ReactSocketSecureServer($webSock, $loop, [
'local_cert' => '/etc/letsencrypt/live/mydomain.com/fullchain.pem', // path to your cert
'local_pk' => '/etc/letsencrypt/live/mydomain.com/privkey.pem', // path to your server private key
'allow_self_signed' => FALSE, // Allow self signed certs (should be false in production)
'verify_peer' => FALSE
]);
$webServer = new RatchetServerIoServer(
new RatchetHttpHttpServer(
new RatchetWebSocketWsServer(
new Chat()
)
),
$webSock, $loop
);

$webServer->run();
Its running perfectly good for now, even with daemon :)

Hello @michaelphipps
As I mentioned earlier that I am using
`use RatchetServerIoServer;
use RatchetHttpHttpServer;
use RatchetWebSocketWsServer;
use MyAppChat;

require dirname(DIR) . '/vendor/autoload.php';
require_once dirname(DIR) . '/src/chat.php';

$loop = ReactEventLoopFactory::create();
$webSock = new ReactSocketServer('0.0.0.0:8088', $loop);
$webSock = new ReactSocketSecureServer($webSock, $loop, [
'local_cert' => '/etc/letsencrypt/live/mydomain.com/fullchain.pem', // path to your cert
'local_pk' => '/etc/letsencrypt/live/mydomain.com/privkey.pem', // path to your server private key
'allow_self_signed' => FALSE, // Allow self signed certs (should be false in production)
'verify_peer' => FALSE
]);
$webServer = new RatchetServerIoServer(
new RatchetHttpHttpServer(
new RatchetWebSocketWsServer(
new Chat()
)
),
$webSock, $loop
);

$webServer->run();`
to run a wss server. As you suggested I am running it through the daemon
[Unit]
Description=Chat Server

  [Service]
  ExecStart=/usr/bin/php /var/www/vhosts/domain.com/bin/chat-server.php
  StandardOutput=null
  Restart=always
  RestartSec=10

  [Install]
  WantedBy=multi-user.target

It runs well, all goes well- until it is inactive for some time, it still establishes wss connection but does not send back responses. It will be great if you can suggest something for it.

I also noticed websockets shutting down after a few minutes. I found that it was the client that was timing out. If I refreshed the client connections, everything worked again. You'll recall I mentioned a periodic timer a while back! Now there's an event loop we can include it!

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

require dirname(DIR) . '/vendor/autoload.php';
require_once dirname(DIR) . '/src/chat.php';

$chat = new Chat();

$loop = React\EventLoop\Factory::create();
$webSock = new React\Socket\Server('0.0.0.0:8088', $loop);
$webSock = new React\Socket\SecureServer($webSock, $loop, [
'local_cert' => '/etc/letsencrypt/live/mydomain.com/fullchain.pem', // path to your cert
'local_pk' => '/etc/letsencrypt/live/mydomain.com/privkey.pem', // path to your server private key
'allow_self_signed' => FALSE, // Allow self signed certs (should be false in production)
'verify_peer' => FALSE
]);
$webServer = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer(
new Ratchet\WebSocket\WsServer(
$chat
)
),
$webSock, $loop
);

$loop->addPeriodicTimer(30, function() use ($chat) {
    foreach ($chat->clients as $client){
        $message['event']="heartbeat";    
        $client->send(json_encode($message));
    }
});

$loop->run();

Changes I've made:
Instantiated the Chat class so it can be called by your server and the periodic timer.
$chat = new Chat();

Added Periodic Timer Section to create a heartbeat that should keep the channel open

$loop->addPeriodicTimer(30, function() use ($chat) {
    foreach ($chat->clients as $client){
        $message['event']="heartbeat";
        $client->send(json_encode($message));
    }
});

I don't know how you send chat messages to attatched clients. My code just iterates over all connected clients and sends a heartbeat message. _BTW: If you aren't already, consider wrapping your messages in json, so you can send different types of messages._

Running the loop rather than the webserver
$loop->run();

That should be enough to keep the server responding to responses, but I want to add one more thing.

It's worth learning how autoloading works!
I should note that I'm still quite new to using PSR-4 (autoloading) but I think it's an important skill to have. There is a really good basic setup for autoloading in composer's documentation here: https://getcomposer.org/doc/01-basic-usage.md#autoloading

require dirname(DIR) . '/vendor/autoload.php';
require_once dirname(DIR) . '/src/chat.php';

would become

$loader = require  __DIR__.'/vendor/autoload.php';
$loader->addPsr4(MyApp\\', __DIR__.'/src');  

and you would add this to your composer.json

    "autoload": {
        "psr-4": {
            "MyApp\\": "src"
        }
    }

then you need to run
> composer dump-autoload
So that autoload knows about your Chat class.

Dear @michaelphipps
Thank you very much for a detailed response. I need some time to process it all and give feedback. I will let you know. Thank you very much once again.

Dear @michaelphipps
Thank you very much once again for all your help and very detailed replies. I wanted to give you my feedback after having a degree of confidence over my testing. So my issue was that after some time of inactivity my Ratchet server will apparently stop working, and we thought it was a Ratchet issue and we need to have a heartbeat loop to keep it running. Turned out to be that by default MySQL will timeout after 8 hours of inactivity and will disconnect the connection. So for now I have added a corn job for root which restarts the Ratchet server after 8 hours and its working for me.
0 0,8,16 * * * systemctl restart chat-server.service
I intend to make MySQL timeout to never as my next step. I have not done any load testing for now though. Thank you very much once again. I thought to give you an update.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Rinum picture Rinum  路  6Comments

elovin picture elovin  路  3Comments

Opice picture Opice  路  4Comments

johankitelman picture johankitelman  路  3Comments

mhlz picture mhlz  路  9Comments