amphp / websocket-client

Async WebSocket client for PHP based on Amp.

Home Page:https://amphp.org/websocket-client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to get pings from an open connection?

timothymarois opened this issue · comments

Hello,

I am using the readme example here: https://github.com/amphp/websocket-client/blob/master/README.md

Issue:
The while loop will only run when a message is sent through the stream, no message, nothing ever can happen since it's in idle mode waiting.

Solution:
How can I receive the ping or have the while loop run on the ping, and still collect the messages?

For instance, I would like to have control on checking some information, (such as the socket should remain open) however, it can only check that when a message is coming through the stream, this limits the script as it would only ever execute when there's an activity, thus waiting forever if no information is ever sent.

Pings are standard in web sockets based on the RFC: https://tools.ietf.org/html/rfc6455

in the Rfc6455Connection connection class, there are pings, but there is no documentation on how to access this or use this directly.

It would be cool to run the while loop on the ping and check if there is a message at the same time, is this possible?

Pings are automatically handled for you, so there's nothing to worry about.

You can always create a new coroutine using Amp\call or Amp\asyncCall to do things concurrently, which are explained in the framework's base package amphp/amp.

I'm currently using the websocket-client package on a laravel framework. Do you have an example of doing tasks concurrently while waiting for messages to come in? I'm unable to find that functionality.

You can find a basic example in examples/coroutines/concurrency.php, does that help?

It seems like all questions have been answered. If not, please respond with further questions, the issue can always be re-opened. 👍

Hey @kelunik It doesn't seem like that did it, though its helpful to know, but from testing, even if you try to run both, it still requires the first function to finish, so if the first function was a never-ending while loop, the second would never actually execute. That's why I'm trying to figure a way to run my websockets based on pings coming in so that I can run other tasks, right now once it connects to the websocket I'm stuck waiting for information before the script can do anything else. Do you know a solution to this based on the features amp has? Unless I'm missing something.

Based on the example in my first post, I want to control

while ($message = yield $connection->receive()) 
{
}

this loop, but I can't because it's waiting for messages, if messages never arrive, I never have control. But if it was related to pings being received, then I could decide how to handle it better.

That's why you can have two while loops running concurrently. Could you clarify what you're trying to check so I can help?

@kelunik Right, that's what I thought I could try, now maybe I'm doing something wrong, I'm not very well versed with Amp, but here is an example I tried to do just to see if it could work.

In conclusion, what I'm trying to do is have the control to end the while loop with a break when I want to shut down the script and let it run any tasks afterward etc.

asyncCall(function() use ($fn) {

      \Amp\Loop::run(function () use ($keys, $fn)
      {
                try 
                {
                   

                    $connection = yield connect($uri);

                    yield $connection->send('...');
                    
                    while ($message = yield $connection->receive()) 
                    {
                        $payload = yield $message->buffer();

                        $r = $fn($payload);

                        if ($r == false) {
                            $this->warn('Connection closed.');
                            $connection->close();
                            break;
                        }
                    }
                }
                catch (\Exception $e) {
                    $this->isError($e->getMessage(),false);
                }
         });
 });
        
asyncCall(function () 
{
            while (true) 
            {
                if (!$this->isActive()) die('dead');

                yield new Delayed(1000);
            }
});

The second asyncCall I tried to get to run concurrently, but it didn't work. The idea behind that was, could I test having control over completely killing the script, this is bad, but just an example to see if it would actually work. It would check if the script is still "active" otherwise I threw a die, the script never ran because of the first while loop holding up execution. Thoughts?

Again, if there is a way to control the first while loop, that would be the perfect solution, being able to do something like

while ($message = yield $connection->receive()) 
 {
         if (!$this->isActive()) break;
}

but right now, that would only check if a message came through.

Thanks, hope this makes sense.

@kelunik Actually. I messed that up. It does work, I just need to move the loop call outside of the first method and call this \Amp\Loop::run(); as in the example. That was my fault. And it runs concurrently!

The next question is though, how could I end the first while loop since its always looking for a message, I want to gracefully end the script itself when I disable the script. As seen in my example. Concurrent functionality is awesome! But I still would like control over ending that while loop. Any thoughts on how to read the connection information differently to do so?

You can spawn new coroutines everywhere, so you can make use of the connection there and close it to automatically stop the other while loop. Something like that:

Loop::run(function () {
    try {
      $connection = yield connect($uri);
      
      asyncCall(function () use ($connection) {
        while (true) {
          if (!$this->isActive()) {
            $connection->close();
            break;
          }
          
          yield Amp\delay(1000);
        }
      });

      yield $connection->send('...');

      while ($message = yield $connection->receive()) {
          $payload = yield $message->buffer();

          $r = $fn($payload);

          if ($r == false) {
              $this->warn('Connection closed.');
              $connection->close();
              break;
          }
      }
  } catch (\Exception $e) {
      $this->isError($e->getMessage(),false);
  }
});

Oh, nice. I never thought of it that way, that's good. Thank you for that. We can close this thread.

@kelunik I do want to say thank you for that quick snippet/advice, this saved so much time I was trying to accomplish in other ways. Much appreciated!

I'm happy I could help.

@kelunik can you help?
stackoverflow: https://goo.su/qk4g

@Konstantin-seo I can if you post the question in English, either here or on Stack Overflow.

@kelunik then ask here

use Amp\ByteStream\StreamException;
use Amp\Loop;
use Amp\Websocket;
use Amp\Websocket\Client;
use Amp\Websocket\Client\Connection;
use Amp\Websocket\Client\Handshake;
use Amp\Websocket\ClosedException;
use Amp\Websocket\Message;
use Amp\Websocket\Options;
use Amp\Delayed;

use function Amp\asyncCall;
use function Amp\Websocket\Client\connect;
    
    Amp\Loop::run(function () {
    
    $handshake = (new Handshake('wss://mg-s1.site.ru/api/bot/v1/ws?events=message_new'))->withHeader('x-bot-token', '0000000000000000000000000000000000000000000000000');

    $connection = yield connect($handshake);

 asyncCall(function () use ($connection) {
    while (true) {
      if (!$this->isActive()) { //**error on this line**
        $connection->close();
        break;
      }

      yield Amp\delay(1000);
    }
  });

    yield $connection->send('Hello!');

    try {
        while ($message = yield $connection->receive()) {
            $payload = yield $message->buffer();
            
            $result = json_decode($payload, true);
            
            //handler code

        } 
    } catch (ClosedException $e) {
            logFileEvent ('Connection break. sleep 30 sec');
            logFile ('Errors: ' . $e->getMessage());
            sleep(30);
        } catch (AssertionError $e) {
            logFile ('Errors: ' . $e->getMessage());
            $connection->close();
        } catch (Error $e) {
            logFile ('Errors: ' . $e->getMessage());
            $connection->close();
        } catch (StreamException $e) {
            logFile ('StreamException: ' . $e->getMessage());
            $connection->close();
        }
});

give error: Uncaught Error: Using $this when not in object context in

code witch asyncCall copied from your answer in stackoverflow

the task is to avoid a fatal error # 1006 when the

This assumes you're inside a class and that class has an isActive() method. You can remove the whole asyncCall() around it, if you don't need such special handling.

How can I get around the error with my code:

    Connection closed abnormally while awaiting message; Code 1006 (ABNORMAL_CLOSE); Reason: "TCP connection closed unexpectedly"
    Connection closed abnormally while awaiting message; Code 1008 (POLICY_VIOLATION); Reason: "Exceeded unanswered PING limit"

the script crashes and you have to re-run the script
Please help me fix my code.

You can catch these exceptions and reconnect using a while loop for example.

You can catch these exceptions and reconnect using a while loop for example.

can you please write a code example on how to do this?

You can catch these exceptions and reconnect using a while loop for example.

can you please write a code example on how to do this?

I've used this method which keeps retrying for any exception or even sigfault etc.

https://howtotrainyourrobot.com/keeping-a-process-running-with-a-3-line-bash-script/

You can catch these exceptions and reconnect using a while loop for example.

can you please write a code example on how to do this?

I've used this method which keeps retrying for any exception or even sigfault etc.

https://howtotrainyourrobot.com/keeping-a-process-running-with-a-3-line-bash-script/

it's a crutch

@kelunik
why the script swears at the absence of a method "withQueuedPingLimit()"
does it really not exist?

what is the replacement to lower the ping and there was no error

"1008 (POLICY_VIOLATION); Reason: "Exceeded unanswered PING limit"?