I'm currently attempting to connect to two different socket servers. One of them is essentially an IRC connection, the other is an interface server that I've made myself. These two loops need to be able to communicate with each other, but I'm having difficulty actually sending a message from one connection to another.
Here's what I've been trying as a simplified way of injecting the message, the comments are not very confident because I'm honestly not sure where I'm going wrong:
<?php
require __DIR__ . '/vendor/autoload.php';
// EventLoop the way I understand it is designed to split up your loops and
// run them one after another so you can kind of multithread, even though
// it's just one step of the loop at a time.
$loop = \React\EventLoop\Factory::create();
// Verbose defintion of connectors mainly trying to just gain extra control
// and so I could see how each thing was defined.
$reactConnector = new \React\Socket\Connector($loop, [
'dns' => '8.8.8.8',
'timeout' => 10
]);
$connector = new \Ratchet\Client\Connector($loop, $reactConnector);
// Connect as a client to the development Socket server. This is all just
// from the Pawl github essentially.
//
// The connection is successful every time.
$ws = $connector('ws://0.0.0.0:5069', [], ['Origin' => 'http://localhost']);
->then(function(Ratchet\Client\WebSocket $conn) {
// Simple echo on message received for the test.
$conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
});
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
});
$conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
// Instead of including a second socket connector I decided to use a simple
// timer and try to get it to use the client connection above to send
// messages to the socket server.
//
// The problem is here, I can't get the socket server to send a message
// from outside of the ->then();
$loop->addPeriodicTimer(1, function () use ($ws) {
$ws->then(function (Ratchet\Client\WebSocket $conn) {
$conn->send('Figured out?');
});
});
$loop->run();
I'd really like to be able to send messages from one connection the the other through some sort of $ws->send('message');, but I can't for the life of me figure out how.
Ahhh Finally a question I can answer!! I spent most of yesterday working through my own Ratchet/Pawl client that had to have addPeriodicTimer loops to send content at periodic times. It took some poking around but I made it work by placing the $loop->addPeriodicTimer() call INSIDE of the connector block, after the ->then(function(Ratchet\Client\WebSocket $conn) part and before the $conn->on('message'...) calls. Also for the $loop->addPeriodicTimer calls make sure to add the use clause passing in the connection ... and make sure to add the use clause passing in the $loop to the connector.
<?php
require __DIR__ . '/vendor/autoload.php';
// EventLoop the way I understand it is designed to split up your loops and
// run them one after another so you can kind of multithread, even though
// it's just one step of the loop at a time.
$loop = \React\EventLoop\Factory::create();
// Verbose defintion of connectors mainly trying to just gain extra control
// and so I could see how each thing was defined.
$reactConnector = new \React\Socket\Connector($loop, [
'dns' => '8.8.8.8',
'timeout' => 10
]);
$connector = new \Ratchet\Client\Connector($loop, $reactConnector);
// Connect as a client to the development Socket server. This is all just
// from the Pawl github essentially.
//
// The connection is successful every time.
$ws = $connector('ws://0.0.0.0:5069', [], ['Origin' => 'http://localhost']);
->then(function(Ratchet\Client\WebSocket $conn) use ( $loop ) {
$loop->addPeriodicTimer(1, function () use ( $conn ) {
$conn->send('Figured out?');
});
// Simple echo on message received for the test.
$conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
});
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
});
$conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
$loop->run();
Related
I am using PHP 7.2 on a website hosted on Amazon. I have a code similar to this one that writes a record in the MongoDB:
Database connection class:
class Database {
private static $instance;
private $managerMongoDB;
private function __construct() {
#Singleton private constructor
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new Database();
}
return self::$instance;
}
function writeMongo($collection, $record) {
if (empty($this->managerMongoDB)) {
$this->managerMongoDB = new MongoDB\Driver\Manager(DB_MONGO_HOST ? DB_MONGO_HOST : null);
}
$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert($record);
try {
$result = $this->managerMongoDB->executeBulkWrite(
DB_MONGO_NAME . '.' . $collection, $bulk, $writeConcern
);
} catch (MongoDB\Driver\Exception\BulkWriteException $e) {
// Not important
} catch (MongoDB\Driver\Exception\Exception $e) {
// Not important
}
return $result->getInsertedCount() > 0;
}
}
Execution:
Database::getInstance()->writeMongo($tableName, $dataForMongo);
The script is working as intended and the records are added in MongoDB.
The problem is that connections are not being closed at all and once there are 500 inserts (500 is the limit of connections in MongoDB on our server) it stops working. If we restart php-fpm the connections are also reset and we can insert 500 more records.
The connection is reused during the request, but we have requests coming from 100s of actual customers.
As far as I can see there is no way to manually close the connections. Is there something I'm doing wrong? Is there some configuration that needs to be done on the driver? I tried setting socketTimeoutMS=1000&wTimeoutMS=1000&connectTimeoutMS=1000 in the connection string but the connections keep staying alive.
You are creating a client instance every time the function is invoked, and never closing it, which would produce the behavior you are seeing.
If you want to create the client instance in the function, close it in the same function.
Alternatively create the client instance once for the entire script and use the same instance in all of the operations done by that script.
I am using ReactPHP for TCP listener component. This component listens for incoming connections and exchanges data with them. $connections array is updated as clients connect/disconnect from listener.
$loop = React\EventLoop\Factory::create();
$connections = [];
$socket = new React\Socket\Server($loop);
$socket->on('connection', function ($conn) use($loop, $db){
global $connections;
$connections[] = $conn;
$conn->on('data', function ($data) use ($conn,$loop, $db) {
global $connections;
// ...
// ...
$conn->on('close', function ($conn) use($loop, $db){
global $connections;
if(($key = array_search($conn, $connections, true)) !== FALSE) {
unset($connections[$key]);
}
});
});
$socket->listen(16555, '127.0.0.1');
$loop->run();
If client is connected via telnet 'close' will be emitted so I can remove closed connection from $connection array.
However, I have problem with some devices that connect to my listener too. If I turn off device 'close' will not be emitted.
I tried to solve problem with periodical timer:
$loop->addPeriodicTimer(10, function () use($db, $loop){
global $connections;
foreach($connections as $c) {
$remoteAddress = $c->getRemoteAddress();
$metaData = #stream_get_meta_data($c->stream);
if(!$metaData) {
if(($key = array_search($c, $connections, true)) !== FALSE) {
unset($connections[$key]);
}
}
}
});
But seems that it is not reliable enough. Function stream_get_meta_data returns valid metadata array even though client is disconnected.
It is some while since this question was asked, but I've found what works for me is to use an SplObjectStorage() to be the connection pool. This is a collection which doesn't (externally) have an index. It works quite well for connections.
https://www.php.net/manual/en/class.splobjectstorage.php
I think the source of your original problem is that you are unsetting an element within a foreach, which does not automatically update the keys, and you can end up with your objects out of sequence.
In order to iterate over a collection (or an array) with code where you may be removing one or more elements while within the loop, it can be safer to use clone.
So where your pool is:
$connections_pool = new SplObjectStorage();
To iterate you would do (per your original request)
$loop->addPeriodicTimer(10, function () use($db, $loop){
global $connections_pool;
foreach(clone($connections_pool) as $c) {
$remoteAddress = $c->getRemoteAddress();
$metaData = #stream_get_meta_data($c->stream);
if(!$metaData) {
$connections_pool->offsetUnset($c);
}
}
});
I'm creating a redis connection using phpredis client
$redis = new Redis();
$redis->pconnect(loclahost, 6336, 2) ;
$redis->select(15);
Now I used the $redis object inside an infinite loop.
while(true){
///using redis connection object.
}
Around 54 such individual processes were running but once or twice in a day I get an error like "read error on connection".
Please help me to fix it.
I would think something like this would work. NOTE I have not tested this, and I have not written PHP in a pretty long time.
function redisConnection() {
try {
$redis = new Redis()
$redis->pconnect(localhost, 6336, 2);
$redis->select(15);
$redis->ping();
return $redis;
} catch (Exception $e) {
throw new Exception("Can not connect: " . $e->getMessage());
}
}
$redis = redisConnection();
while (true) {
try {
$redis->ping();
} catch {
$redis = redisConnection();
}
// Rest of code
}
Currently, i am trying a simple code of sending/receiving messages using ZMQ. The code is as below
/* Create new queue object, there needs to be a server at the other end */
$queue = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REQ);
$queue->connect("tcp://127.0.0.1:5555");
/* Assign socket 1 to the queue, send and receive */
$retries = 5;
$sending = true;
/* Start a loop */
do {
try {
/* Try to send / receive */
if ($sending) {
echo "Sending message\n";
$queue->send("This is a message", ZMQ::MODE_NOBLOCK);
$sending = false;
} else {
echo "Got response: " . $queue->recv(ZMQ::MODE_NOBLOCK) . "\n";
echo 'Complete';
break;
}
} catch (ZMQSocketException $e) {
/* EAGAIN means that the operation would have blocked, retry */
if ($e->getCode() === ZMQ::ERR_EAGAIN) {
echo " - Got EAGAIN, retrying ($retries)\n";
} else {
die(" - Error: " . $e->getMessage());
}
}
/* Sleep a bit between operations */
usleep(5);
} while (--$retries);
When i run this script in console, my output is
Sending message
Got response:
Complete
I believe that though there are no errors thrown, but still my message is not actually sent. I also ran netstat command but i didn't found any process listening on port 5555. Ideally there should be one(current). But no exception is thrown while making connection.
Is there something which i am missing?
When you say no process is listening on port 5555, it probably means that your server is not up and running. Your client will not throw any errors even if there is no server, it just sets up and waits for the server to come online (with your particular setup here, at least).
In this case, since you're using non-blocking mode, it'll send your message on the first pass through the loop (why are you sending the message in the loop at all?), but it won't actually send anything because the server isn't there. Then it'll attempt to receive on the second pass through the loop, but since the socket isn't ready to receive it looks like it'll just fail silently, without throwing an exception. Since no exception is thrown, it gets to your break statement, quits the loop, and you're done.
First things first, your send call should happen before the loop, it's something you want to do only once.
Then, when you recv, store the result in a variable and test for emptiness. Leave the try/catch code. The result should be something like this:
/* Create new queue object, there needs to be a server at the other end */
$queue = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REQ);
$queue->connect("tcp://127.0.0.1:5555");
/* Assign socket 1 to the queue, send and receive */
$retries = 5;
echo "Sending message\n";
$queue->send("This is a message", ZMQ::MODE_NOBLOCK);
/* Start a loop */
do {
try {
/* Try to receive */
$response = $queue->recv(ZMQ::MODE_NOBLOCK);
if (!empty($response)) {
echo "Got response: " . $response . "\n";
echo 'Complete';
break;
}
else {
echo "we probably haven't even sent the original request yet, retrying ($retries)\n";
}
}
catch (ZMQSocketException $e) {
/* EAGAIN means that the operation would have blocked, retry */
if ($e->getCode() === ZMQ::ERR_EAGAIN) {
echo " - Got EAGAIN, retrying ($retries)\n";
} else {
die(" - Error: " . $e->getMessage());
}
}
/* Sleep a bit between operations */
usleep(5);
} while (--$retries);
echo "END OF LINE";
... keep in mind this is a minor fix to the client code that makes it behave a little more rationally, I still believe your actual problem is that the server isn't running.
Please add the server code. You are showing the client code. Since the problem seems to be in the server, and you also state that the server is not visible with netstat, the problem is most probably there.
My amqp extension version is 1.0.1 & AMQP protocol version is 0-9-1
get messages from queue :
<?php
try {
$conn = new AMQPConnection() ;
$conn->setLogin('guest') ;
$conn->setPassword('guest') ;
$conn->connect() ;
if ($conn->isConnected()) {
$channel = new AMQPChannel($conn) ;
if ($channel->isConnected())
{
$queue = new AMQPQueue($channel) ;
$queue->setName('test_queue') ;
$queue->setFlags(AMQP_DURABLE | AMQP_AUTODELETE) ;
$queue->declare() ;
$messages = $queue->get(AMQP_AUTOACK) ;
print_r($messages->getBody()) ;
}
} else {
echo "connect failure ... " ;
}
$conn->disconnect() ;} catch (Exception $e) {
echo $e->getMessage() ;}?>
and it doesn't work ..
Server channel error: 406, message: PRECONDITION_FAILED - parameters for queue 'test_queue' in vhost '/' not equivalent
It seems to me that the queue already exists and it was declared (created) previously with different parameters in the vhost. Queues need to be declared exactly with the same parameters every time (or deleted and recreated with the desired parameters). Try deleting the queue via the management plugin (http://www.rabbitmq.com/management.html) and then running your script again
If your queue has already been created you don't need to create it (using 'declare' method) and bind with exchange once again. IMHO you shouldn't do it as a) these actions require administrative privileges b) it's enough to to it only once c) you might not have got administrative rights on production and your code would be broken.
I believe it's better to create and bind all required queues with management console or any other tool you like and then receive messages this way
// consider using connection more than once. that's only for illustration purposes.
$connection = new AMQPConnection([ put your credentials here ]);
$connection->connect();
if(!$connection->isConnected()) {
throw new Exception('Connection failed.');
}
$chnlObj = new AMQPChannel($connection);
$queObj = new AMQPQueue($chnlObj);
$queObj->setName('yourQueueName');
echo $queObj->get(AMQP_AUTOACK)->getBody();
// consider using connection more than once. that's only for illustration purposes.
$connection->disconnect();