How do I use a Ratchet\Server\IoServer object after run executed? - php

I want to run a function that iterates through a generator class. The generator functions would run as long as the Ratchet connection is alive. All I need to do is to make this happen after the run method is executed:
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;
require dirname(__DIR__) . '/xxx/vendor/autoload.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8180,
'0.0.0.0'
);
$server->run();
This is the method I need to run in the server after it is started:
function generatorFunction()
{
$products = r\table("tableOne")->changes()->run($conn);
foreach ($products as $product) {
yield $product['new_val'];
}
}
Previously I was calling the function before $server->run() like this:
for ( $gen = generatorFunction(); $gen->valid(); $gen->next()) {
var_dump($gen->current());
}
$server->run();
But this doesn't allow the client to establish a connection to the Ratchet server. I suspect it never comes to $server->run() as the generator class is being iterated.
So now, I want to start the server first, then call this generator method so that it can keep listening to changes in rethinkdb.
How do I do that?

Let's start by example:
<?php
require 'vendor/autoload.php';
class Chat implements \Ratchet\MessageComponentInterface {
function onOpen(\Ratchet\ConnectionInterface $conn) { echo "connected.\n"; }
function onClose(\Ratchet\ConnectionInterface $conn) {}
function onError(\Ratchet\ConnectionInterface $conn, \Exception $e) {}
function onMessage(\Ratchet\ConnectionInterface $from, $msg) {}
}
$loop = \React\EventLoop\Factory::create(); // create EventLoop best for given environment
$socket = new \React\Socket\Server('0.0.0.0:8180', $loop); // make a new socket to listen to (don't forget to change 'address:port' string)
$server = new \Ratchet\Server\IoServer(
/* same things that go into IoServer::factory */
new \Ratchet\Http\HttpServer(
new \Ratchet\WebSocket\WsServer(
new Chat() // dummy chat to test things out
)
),
/* our socket and loop objects */
$socket,
$loop
);
$loop->addPeriodicTimer(1, function (\React\EventLoop\Timer\Timer $timer) {
echo "echo from timer!\n";
});
$server->run();
To achieve what you need you don't have to run the loop before or after the $server->run() but it needs to be run simultaneously.
For that you need to get deeper than Ratchet - to ReactPHP and its EventLoop. If you have access to the loop interface then adding a timer (that executes once) or a periodic timer (every nth second) is a piece of cake.

Related

PHP Swoole: how to make variables persistent between requests?

As a simple example, say I simply want to increment a counter when someone connects. The code I have is
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
$server = new Server("0.0.0.0", 9502);
$users = [];
$server->on("Start", function(Server $server){
echo "Swoole WebSocket Server is started at http://127.0.0.1:9502\n";
});
$server->on('Open', function(Server $server, Swoole\Http\Request $request){
global $count;
array_push($users, $request->fd);
var_dump($users);
});
Note: fd is simply the connection ID of the user (maybe short for file descriptor?), so I'm basically trying to get a simple array of all the users that have connected - the problem is though, this value isn't persistant between requests - so when the 2nd user connects, the array becomes empty again
Does anyone know of a way to fix this? I know I could use a database, but that seems very wasteful/inefficient when I want to create a realtime site - so that's why I want to store/access all the data within the php script, if possible...without using any external storage
Not tested but basicly do:
<?php
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\Table;
$server = new Server("0.0.0.0", 9502);
$table = new Table(1024);
$table->column('user_id', Swoole\Table::TYPE_STRING, 64);
$table->create();
$server->on("Start", function(Server $server){
echo "Swoole WebSocket Server is started at http://127.0.0.1:9502\n";
});
$server->on('Open', function(Server $server, Swoole\Http\Request $request) use ($table) {
$table->set($request->fd,['user_id'=>$request->fd]);
var_dump($table->count());
});
Read more about the possibilities here: openswoole.com/docs/modules/swoole-table

Ratchet Websocket async mysql best practice

I've build a Websocket chat based on ratchet that uses reactphp async mysql and just got a couple of questions to make sure I'm doing things right, as I couldn't really find any examples for that case out there.
The use case of the Socket is a livechat that handles many different pages at the same time, about 20 pages with 1000+ Users.
Actually this works for now with 100 users, but I have doubts about the database connection to block or queue too many queries with more than thousand users at the same time.
I need to make sure the server can handle everything fast.
Ratchet: https://github.com/ratchetphp/Ratchet
Mysql: https://github.com/friends-of-reactphp/mysql
So, the Socket will be used by different applications, each of them has it's own database where the data has to be stored and published, which means there are multiple Lazy connections created, this is done by the Ratchet Pusher.
There is one database locally on the Websocket server that stores all connected interfaces in one table, out of there the connections are distributed.
The push-server starts the event loop and passes the mysql factory to the pusher constructor (push-server.php):
require dirname(__DIR__).'/websocket/vendor/autoload.php';
use React\MySQL\Factory;
use React\MySQL\QueryResult;
$loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);
$pusher = new websocket01\Pusher($factory);
$webSock = new React\Socket\Server('0.0.0.0:8090', $loop);
$webServer = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer(
new Ratchet\WebSocket\WsServer(
new Ratchet\Wamp\WampServer(
$pusher
)
)
),
$webSock
);
$loop->run();
The Pusher then creates a lazy connection for each connected Interface in the constructor, and thats my second question: Is it better to create just one async db connection at all and keep it alive (like now), or is is better to reopen the connection at every client interaction ?
(Pusher.php):
protected $connection;
protected $subconnections = array();
public function __construct($factory){
//Main Websocket db
$uri = 'localhost...';
$this->connection = $factory->createLazyConnection($uri);
//Create array with Connections to Sub dbs for every entry in the interface table
$stream = $this->connection->queryStream('SELECT * from interfaces');
$stream->on('data', function ($interface) use ($factory) {
if (!array_key_exists($interface['interface_dbhost'], $this->subconnections)) {
$uri = "".$interface['interface_dbuser'].":".$interface['interface_dbpass']."#".$interface['interface_dbhost'].":".$interface['interface_dbport']."/".$interface['interface_dbname']."";
$this->subconnections[$interface['interface_dbhost']] = $factory->createLazyConnection($uri);
}
});
$stream->on('end', function () {
echo 'Completed.';
});
}
The main (local) database will still be used to save clients data, errors, verify tokens etc.
The right database connection will then be chosen in onopen, onsubscribe, onpublish... automatically out of the subconnections array, depending on the application the client comes from, and the action he wants to perfom, for e.g:
public function onPublish(ConnectionInterface $conn, $conversation_id, $event, array $exclude, array $eligible) {
$obj = json_decode($event);
$interface = $obj->frontend;
$channel = $obj->channel;
$db = $this->selectDatabase($conversation_id, $interface); //Choose right database connection
$clienthandler = new Clienthandler\Clienthandler($this->connection);
$action = $this->processRequest($db,$obj, $conversation_id, $clienthandler, $operatorhandler, $channel);
}
I'm also passing the local db connection to the clienthandler too, as I need it there to log actions.
public function selectDatabase($conversation_id, $interface) {
if(array_key_exists($interface, $this->subconnections)) {
$dbconn = $this->subconnections[$interface];
$dbconn->ping()->then(function () {
echo 'Connection alive' . PHP_EOL;
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
}
}
return $dbconn;
}
The clienthandler then saves the data and broadcasts it to all subscribers
I'm open for any advice :-)
Thank you for your help

Should an existing ReactPHP loop be reused?

Should a single ReactPHP loop be used for multiple purposes as shown below, or should a new loop be created and used for each purpose? If reused, the loop is obviously already running, so how does one ensure it is not inadvertently executed before completely configured such as in my process example (i.e. between $process->start($loop) and $process->stdout->on(...))?
<?php
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) use($loop) {
$connection->on('data', function ($data) use ($connection, $loop) {
$loop->addTimer(10, function() {
syslog(LOG_INFO, 'Do something upon non-repeating timer completion');
});
$process = new \React\ChildProcess\Process($cmd);
$process->start($loop);
$process->stdout->on('data', function ($chunk) {
syslog(LOG_INFO, 'Do something with $chunk');
});
});
});
$loop->run();
You should only use one loop and share it within your application. What I always do is first ensure everything is set up and then start the loop. You can do this with an event dispatcher like PSR-14 defines for example.

How to run a computation while running a websocket server in PHP?

I have the following scenario:
I have an API built with the Slim PHP framework. I am using the PHP lib Ratchet to run a WebSocket server. Once the WebSocket server is started, I want to run a function that does some computation while the server is running.
So far, inside my API, I have a route that calls the MyMethod method of a class MyClass. Inside the class, I have the following:
class MyClass {
public $calculation_status;
public function MyMethod() {
$server = IoServer::factory(
new HttpServer(
new WsServer(
new messengerApp($this)
)
),
8080
);
$this->doCalculationAsynchronously()->then(
function ($result) {
$this->calculation_status = 'finished';
},
function ($reason) {
$this->calculation_status = 'stopped';
},
function ($update) {
$this->calculation_status = 'still working...';
}
}
$server->run($this);
}
public function doCalculationAsynchronously() {
$deferred = new Deferred();
$this->computeSomethingAsync(function ($error = null, $result) use ($deferred) {
if ($error) {
$deferred->reject($error);
} else {
$deferred->resolve($result);
}
});
return $deferred->promise();
}
public function computeSomethingAsync() {
// Simulate a long running calculation
while(true){} // OR sleep(1000000);
return $result;
}
}
So, I'm expecting this to try to start running the asynchronous calculation function, return a promise to MyMethod, and then run my WebSocket server.
The reason for injecting $this into the server is to access my calculation_status property and be able to send it to clients through the WS.
This code is inspired by the example for Deferred in the ReactPHP doc
Note: If I don't have the forever while loop, it goes on and runs the server correctly (but this is synchronous behavior; my goal for the server is to send the calculation status to clients). Injecting the class into the object works fine as well.

Changing supervisor processes dynamically through PHP APIs

I have setup Supervisor, which I use to manager my worker processes.
Now, I want to dynamically change the processes (stop some processes and start new ones) through the use of PHP APIs.
I found this library which seems to be useful for what I am trying. Specifically, I am using this to change the configuration and this to manage supervisor.
I have set this library up and have the following sample code which seems to work well (from here and here)
<?php
require './vendor/autoload.php';
use Supervisor\Supervisor;
use Supervisor\Connector\XmlRpc;
use fXmlRpc\Client;
use fXmlRpc\Transport\HttpAdapterTransport;
use Ivory\HttpAdapter\Guzzle5HttpAdapter;
use Supervisor\Configuration\Configuration;
use Supervisor\Configuration\Section\Supervisord;
use Supervisor\Configuration\Section\Program;
use Indigo\Ini\Renderer;
//Create GuzzleHttp client
$guzzleClient = new \GuzzleHttp\Client(['auth' => ['user', '123']]);
// Pass the url and the guzzle client to the XmlRpc Client
$client = new Client(
'http://127.0.0.1:9001/RPC2',
new HttpAdapterTransport(new Guzzle5HttpAdapter($guzzleClient))
);
// Pass the client to the connector
// See the full list of connectors bellow
$connector = new XmlRpc($client);
$supervisor = new Supervisor($connector);
$processes = $supervisor->getAllProcesses();
foreach ($processes as $key => $processInfo) {
echo $processInfo . "\r\n";
}
try{
$supervisor->stopProcess('video_convert_02');
}
catch(Exception $e){
echo "\r\n Exception-> " . $e->getMessage();
}
$config = new Configuration;
$renderer = new Renderer;
$section = new Supervisord(['identifier' => 'supervisor']);
$config->addSection($section);
$section = new Program('test', ['command' => 'cat']);
$config->addSection($section);
echo "\r\n Config \r\n" . $renderer->render($config->toArray());
//Not sure how to use this config information to launch supervisor processes.
?>
The output to this is as follows:
pdf_convert_00
pdf_convert_01
video_convert_00
video_convert_01
video_convert_02
video_convert_03
Exception-> BAD_NAME: video_convert_02
Config
[supervisord]
identifier = supervisor
[program:test]
command = cat
I have two questions here:
I dont understand why it throws an exception, when I try to stop the process and
How do I use the config information to launch new processes?
I didn't use this library but "BAD_NAME" error means there is no such process with this name in your configuration.
Btw if you need only to call Supervisor API you can use SupervisorXMLRPC library. Which simply forwards your requests to the Supervisor.
$supervisor = new \Supervisor\Api('127.0.0.1', 9001, 'user', '123');
$processes = $supervisor->getAllProcessInfo();
foreach ($processes as $processInfo) {
print_r($processInfo);
}
try {
$supervisor->stopProcess('video_convert_02');
}
catch (\Supervisor\ApiException $e){
echo "\r\n Exception-> " . $e->getMessage();
}

Categories