I need to publish messages from php script, I can publish a single message fine. But now I need to publish different messages in loop, can't find proper way how to do it, here is what I tried:
$counter = 0;
$closure = function (\Thruway\ClientSession $session) use ($connection, &$counter) {
//$counter will be always 5
$session->publish('com.example.hello', ['Hello, world from PHP!!! '.$counter], [], ["acknowledge" => true])->then(
function () use ($connection) {
$connection->close(); //You must close the connection or this will hang
echo "Publish Acknowledged!\n";
},
function ($error) {
// publish failed
echo "Publish Error {$error}\n";
}
);
};
while($counter<5){
$connection->on('open', $closure);
$counter++;
}
$connection->open();
Here I want to publish $counter value to subscribers but the value is always 5, 1.Is there a way that I open connection before loop and then in loop I publish messages
2.How to access to $session->publish() from loop ?
Thanks!
There are a couple different ways to accomplish this. Most simply:
$client = new \Thruway\Peer\Client('realm1');
$client->setAttemptRetry(false);
$client->addTransportProvider(new \Thruway\Transport\PawlTransportProvider('ws://127.0.0.1:9090'));
$client->on('open', function (\Thruway\ClientSession $clientSession) {
for ($i = 0; $i < 5; $i++) {
$clientSession->publish('com.example.hello', ['Hello #' . $i]);
}
$clientSession->close();
});
$client->start();
There is nothing wrong with making many short connections to the router. If you are running in a daemon process though, it would probably make more sense to setup something that just uses the same client connection and then use the react loop to manage the loop instead of while(1):
$loop = \React\EventLoop\Factory::create();
$client = new \Thruway\Peer\Client('realm1', $loop);
$client->addTransportProvider(new \Thruway\Transport\PawlTransportProvider('ws://127.0.0.1:9090'));
$loop->addPeriodicTimer(0.5, function () use ($client) {
// The other stuff you want to do every half second goes here
$session = $client->getSession();
if ($session && ($session->getState() == \Thruway\ClientSession::STATE_UP)) {
$session->publish('com.example.hello', ['Hello again']);
}
});
$client->start();
Notice that the $loop is now being passed into the client constructor and also that I got rid of the line disabling automatic reconnect (so if there are network issues, your script will reconnect).
Related
This file run my server
<?php
class websocket
{
public $ws;
public function start()
{
$this->ws = new swoole_websocket_server('127.0.0.1', 9502);
$this->ws->on('open', function ($ws, $request) {
echo "connection open: {$request->fd}\n";
});
$this->ws->on('message', function ($ws, $frame) {
echo "received message: {$frame->data}\n";
$this->ws->push($frame->fd, json_encode(["hello", "world"]));
});
$this->ws->on('close', function ($ws, $id) {
$this->onClose($id);
});
$this->ws->start();
$this->sendMessage(1, "asdfasdf");
}
public function sendMessage($id,$msg)
{
$this->ws->push($id, "asdf");
}
}
I run it from cli like this:
php -r 'include("websocket.php"); $web = new websocket; $web->start();'
then I open on browser this file
<?php
include ('websocket.php');
$n = new websocket();
$n->ws->push(1, "asdf", 1, true);
and I get this error:
127.0.0.1:51180 [500]: GET /send.php - Uncaught Error: Call to a member function push() on null in /home/ganbatte/Desktop/123/send.php:4
Why is that and how can I fix it?
Right after instanciating the object the $ws property does not have any value yet. Yet you try to access it. It looks like you have to start it first, like this:
include ('websocket.php');
$n = new websocket();
$n->start(); // <-- add this line
$n->ws->push(1, "asdf", 1, true);
However, given that there is a sendMessage() method as well, I guess you should probably use that, but I am not deep into swoole at all.
This looks like the docs you are looking for: Get Started with Swoole
And maybe it is a good idea to read up on the systematic basics there too.
Remember that this send method sends a message "on" the websocket to the attached clients, not from the clients to the server (a client, most likely a browser, has to do that part).
This code snippet explains sendMessage() and push to fds in scopes
`$server->fds = [];
$server->on('open', function (swoole_websocket_server $server, $request)
{
// add fd to scope
$server->fds[$request->fd] = true; // dummy-bool
});
$server->on('close', function ($server, $fd) {
// delete fd from scope
unset($server->fds[$fd]);
});
$server->on('message', function (swoole_websocket_server $server, $frame)
{
$message = "simple test message number: " . rand(1,10000);
// pipe the message to all 9 other workers
for ($i=0; $i < 10; $i++) { // WORKER_NUM
if ($i !== $server->worker_id)
// in this case only workers (no taskworkers)
$server->sendMessage($message, $i);
}
// message to all fds in this scope
testMessageSender($server, $message);
});
$server->on('pipeMessage', function($server, $src_worker_id, $data) {
// send to your known fds in worker scope
testMessageSender($server, $data);
});
function testMessageSender(&$server, $message){
// use only your own scope fds
foreach ($server->fds as $fd => $dummyBool) {
// push to your connected clients
$server->push($fd, $message);
}
}`
Reference:
Swoole Official Discussion
Actually, I am implementing a cron, where I need to call more than 50 APIs. So, I want to implement something like, if an API takes more than 10 seconds to call, I will skip that.
Actually, I am using PHP 5.6. I have tried to implement Round-Robin. I haven't got anything to check if an API takes more than 10 seconds to call.
You can use ReactPHP tools for this purpose. Here examples how to work with timers https://blog.wyrihaximus.net/2015/01/reactphp-timers/
Example:
require 'vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$i = 0;
$loop->addPeriodicTimer(1, function(React\EventLoop\Timer\Timer $timer) use (&$i, $loop) {
if ($i == 0) {
// send request
} elseif($i < 10){
// check response
} else {
// cancel request and cancel timer
$loop->cancelTimer($timer);
}
++$i;
});
$loop->run();
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.
I was playing around with React and wanted to try to get a working timeout function. Following (sort of) the examples and Unit tests from https://github.com/reactphp/promise-timer#timeout I came up with:
use React\Promise\Timer;
$promise = uncertainOperation();
$loop = \React\EventLoop\Factory::create();
Timer\timeout($promise, 1, $loop)->then(
function ($value) {
var_dump($value);
}
,
function ($error) {
var_dump($error);
}
);
$loop->run();
function uncertainOperation() {
return new React\Promise\Promise(
function ($resolve) {
for($i = 0; $i < 30000000; $i++) { }
$resolve("Done");
}
);
}
But this always resolves with "Done" no matter how low I set the time in Timer\timeout. What am I missing?
The issue with your code is that it blocks. And it synchronously resolves the promise. It does nowhere return to the event loop driver, so that it could schedule the timeout watcher.
Try changing your code to use a timeout as simulation of e.g. a network timeout.
function uncertainOperation($loop) {
return new React\Promise\Promise(
function ($resolve) use ($loop) {
$loop->addTimer(5, function () {
$resolve("Done");
});
}
);
}
$loop->run();
Unfortunately, you have to pass around the loop in React.
How should I multithread some php-cli code that needs a timeout?
I'm using PHP 5.6 on Centos 6.6 from the command line.
I'm not very familiar with multithreading terminology or code. I'll simplify the code here but it is 100% representative of what I want to do.
The non-threaded code currently looks something like this:
$datasets = MyLibrary::getAllRawDataFromDBasArrays();
foreach ($datasets as $dataset) {
MyLibrary::processRawDataAndStoreResultInDB($dataset);
}
exit; // just for clarity
I need to prefetch all my datasets, and each processRawDataAndStoreResultInDB() cannot fetch it's own dataset. Sometimes processRawDataAndStoreResultInDB() takes too long to process a dataset, so I want to limit the amount of time it has to process it.
So you can see that making it multithreaded would
Speed it up by allowing multiple processRawDataAndStoreResultInDB() to execute at the same time
Use set_time_limit() to limit the amount of time each one has to process each dataset
Notice that I don't need to come back to my main program. Since this is a simplification, you can trust that I don't want to collect all the processed datasets and do a single save into the DB after they are all done.
I'd like to do something like:
class MyWorkerThread extends SomeThreadType {
public function __construct($timeout, $dataset) {
$this->timeout = $timeout;
$this->dataset = $dataset;
}
public function run() {
set_time_limit($this->timeout);
MyLibrary::processRawDataAndStoreResultInDB($this->dataset);
}
}
$numberOfThreads = 4;
$pool = somePoolClass($numberOfThreads);
$pool->start();
$datasets = MyLibrary::getAllRawDataFromDBasArrays();
$timeoutForEachThread = 5; // seconds
foreach ($datasets as $dataset) {
$thread = new MyWorkerThread($timeoutForEachThread, $dataset);
$thread->addCallbackOnTerminated(function() {
if ($this->isTimeout()) {
MyLibrary::saveBadDatasetToDb($dataset);
}
}
$pool->addToQueue($thread);
}
$pool->waitUntilAllWorkersAreFinished();
exit; // for clarity
From my research online I've found the PHP extension pthreads which I can use with my thread-safe php CLI, or I could use the PCNTL extension or a wrapper library around it (say, Arara/Process)
https://github.com/krakjoe/pthreads (and the example directory)
https://github.com/Arara/Process (pcntl wrapper)
When I look at them and their examples though (especially the pthreads pool example) I get confused quickly by the terminology and which classes I should use to achieve the kind of multithreading I'm looking for.
I even wouldn't mind creating the pool class myself, if I had a isRunning(), isTerminated(), getTerminationStatus() and execute() function on a thread class, as it would be a simple queue.
Can someone with more experience please direct me to which library, classes and functions I should be using to map to my example above? Am I taking the wrong approach completely?
Thanks in advance.
Here comes an example using worker processes. I'm using the pcntl extension.
/**
* Spawns a worker process and returns it pid or -1
* if something goes wrong.
*
* #param callback function, closure or method to call
* #return integer
*/
function worker($callback) {
$pid = pcntl_fork();
if($pid === 0) {
// Child process
exit($callback());
} else {
// Main process or an error
return $pid;
}
}
$datasets = array(
array('test', '123'),
array('foo', 'bar')
);
$maxWorkers = 1;
$numWorkers = 0;
foreach($datasets as $dataset) {
$pid = worker(function () use ($dataset) {
// Do DB stuff here
var_dump($dataset);
return 0;
});
if($pid !== -1) {
$numWorkers++;
} else {
// Handle fork errors here
echo 'Failed to spawn worker';
}
// If $maxWorkers is reached we need to wait
// for at least one child to return
if($numWorkers === $maxWorkers) {
// $status is passed by reference
$pid = pcntl_wait($status);
echo "child process $pid returned $status\n";
$numWorkers--;
}
}
// (Non blocking) wait for the remaining childs
while(true) {
// $status is passed by reference
$pid = pcntl_wait($status, WNOHANG);
if(is_null($pid) || $pid === -1) {
break;
}
if($pid === 0) {
// Be patient ...
usleep(50000);
continue;
}
echo "child process $pid returned $status\n";
}