ReactPHP - Close disconnected connection

705 views Asked by At

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.

1

There are 1 answers

0
Howard Tomlinson On

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);            
        }
    }
});