amphp / websocket-client

Async WebSocket client for PHP based on Amp.
https://amphp.org/websocket-client
MIT License
147 stars 17 forks source link

Reconnecting upon disconnection #20

Closed dotstormz closed 5 years ago

dotstormz commented 5 years ago

There is no example nor can I work out how to reconnect to a socket server upon disconnection. I assume the loop doesn't need to be restarted, but rather the connection needs to be re-established. When the loop starts it only yields the handshake and connection once.

Can someone please provide an example of how I could leverage the $connection->isClosed() method to force another connection to be established?

trowski commented 5 years ago

Sorry about the lack of documentation on the websocket related libraries, it's on my to-do list.

Here is a simple example that continues to loop as long as $stayConnected is true.

<?php

require \dirname(__DIR__) . '/vendor/autoload.php';

use Amp\Websocket\Client\Connection;
use Amp\Websocket\Client\ConnectionException;
use Amp\Websocket\Client\Handshake;
use Amp\Websocket\Message;
use function Amp\Websocket\Client\connect;

Amp\Loop::run(function () {
    $handshake = new Handshake('ws://example.com/ws');

    $stayConnected = true;

    while ($stayConnected) {
        /** @var Connection $connection */
        $connection = yield connect($handshake);

        try {
            /** @var Message $message */
            while ($message = yield $connection->receive()) {
                // Use $message
            }

            // Connection closed normally.
        } catch (ConnectionException $connectionException) {
            // Connection closed due to an error.
        }
    }
});

I would recommend using v1.0.0-rc1 that was recently tagged. The API is much better and includes a Connection::onClose() method if you'd prefer to use an event-style method for handling connection closures.

Feel free to ask any other questions here or in IRC (I do see the messages, but you've always left before I could reply).

dotstormz commented 5 years ago

@trowski Thanks very much for your response, however I'm still a little bit unclear how to perform the reconnection? I've tested your code above and changing the $stayConnected variable doesn't work as expected as it definitely doesn't stop the the connection. I have also used the RC1 version and I can confirm that I have managed to get the onClosed event working. Unfortunately as it's a production application and I don't feel comfortable running from dev-master and tying things to particular commits in composer.

A better approach might be for me to explain the particular issue I'm having which has led to me attempting to implement some reconnection logic ...

There is an issue where the client loop still remains connected (in theory as it hasn't thrown an exception and the application reports as RUNNING in supervisor), however it stops receiving messages from the socket server (Also an Amphp implementation). If I restart the socket client application (handled by supervisor) it starts to receive messages again.

While I concede that this potentially is not an issue related to the client socket, I am attempting to establish logic that ensures that if a connection is closed regardless of whether it from a thrown exception or the server or client closing the connection expectedly it will reconnect.

The hacky solution was for us to restart the loop every hour from a cron to ensure that if it was connection issues caused by the client the connection would be re-established.

In your above example if I was to receive a onClosed or ConnectionException how would I actually go about re-establishing that connection?

Thanks for your help, I really appreciate it. On another note I am also happy to contribute to writing some documentation and further improving any examples once I've completed my use case!

dotstormz commented 5 years ago

@trowski I managed to get it working. It was necessarily your code example that was the issue but rather how I was testing it. What I needed up needing to do was place a delay method in the while loop instead of outside as it wasn't getting the correct context for the $connection object and therefore the object was only ever the first object.

Let me know if I should do anything else to tidy it up. :)

while (true) {
    print 'Connected ...' . PHP_EOL;
    $connection = yield $this->connect();

    // This is just testing code and needed to be in the while loop to be able to get the correct $connection
    Amp\Loop::delay($msInterval = 5000, function () use ($connection) {    
        print 'Current connection ID: ' . ($connection->getHeaders()['sec-websocket-accept'][0]) . PHP_EOL;
        print 'Closing connection ...' . PHP_EOL;
        $connection->close();
    });

    try {
        while ($message = yield $connection->receive()) {
            print 'Received message' . PHP_EOL;
            $payload = yield $message->buffer();
            print $payload . PHP_EOL;
        }
    } catch (ClosedException $e) {
        print 'Re-establish connection' . PHP_EOL;
        $connection = yield $this->connect();
    }
}
trowski commented 5 years ago

@dotstormz $stayConnected was only a flag in case you wanted to actually close the connection and exit the loop re-establishing connections.

In your code snippet, there should not be a call to connect() in the catch block, since it will be called again at the top of the loop.

I also assume the timer is just to simulate disconnection, and not actually present in the production code?

I would encourage you to upgrade to v1.0.0-rc1. There will be no major BC breaks (and very likely no minor BC breaks) before v1.0.0. However, I certainly understand wanting to wait until the stable version is tagged. I can ping you in this thread when it's tagged.