Skip to content

Commit

Permalink
Added the ability to set up a keep alive interval that will close the…
Browse files Browse the repository at this point in the history
… connection if it does not receive a reply in time.
  • Loading branch information
boenrobot committed Jan 26, 2023
1 parent f8d9605 commit 8ff3491
Showing 1 changed file with 46 additions and 0 deletions.
46 changes: 46 additions & 0 deletions src/WebSocket.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Ratchet\Client;
use Evenement\EventEmitterTrait;
use Evenement\EventEmitterInterface;
use React\EventLoop\LoopInterface;
use React\Socket\ConnectionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
Expand Down Expand Up @@ -35,6 +36,10 @@ class WebSocket implements EventEmitterInterface {
* @var \Closure
*/
protected $_close;
/**
* @var callable
*/
private $pongReceiver;

/**
* WebSocket constructor.
Expand Down Expand Up @@ -92,6 +97,10 @@ function(FrameInterface $frame) use (&$streamer) {
$this->emit('ping', [$frame, $this]);
return $this->send($streamer->newFrame($frame->getPayload(), true, Frame::OP_PONG));
case Frame::OP_PONG:
if ($this->pongReceiver) {
$pongReceiver = $this->pongReceiver;
$pongReceiver($frame, $this);
}
return $this->emit('pong', [$frame, $this]);
default:
return $this->close(Frame::CLOSE_PROTOCOL);
Expand Down Expand Up @@ -154,4 +163,41 @@ public function resume()
{
$this->_stream->resume();
}

/**
* Add a timer to ping the server at a regular interval.
*
* For connections that mostly receive data, it can take a lot of time before the connection is determined to be
* silently gone (e.g. due to connectivity issues). With this method, this check can be made easier.
*
* A ping frame is sent at the interval, and if the corresponding pong is not received by the time the next ping
* is scheduled for, the connection is deemed dead, and is closed.
*
* @param LoopInterface $loop The loop to tie the timer to.
* @param int|float $interval The interval at which to trigger the timer, in seconds.
* @return \React\EventLoop\TimerInterface The periodic timer that is tied to the loop given.
* This allows the caller to cancel the timer later.
*/
public function enableKeepAlive(LoopInterface $loop, $interval = 30)
{
$lastPing = new Frame(uniqid(), true, Frame::OP_PING);
$isAlive = true;

$this->pongReceiver = static function(FrameInterface $frame, $wsConn) use (&$isAlive, &$lastPing) {
if ($frame->getPayload() === $lastPing->getPayload()) {
$isAlive = true;
}
};
return $loop->addPeriodicTimer($interval, function() use (&$isAlive, &$lastPing) {
if (!$isAlive) {
$this->close(Frame::CLOSE_ABNORMAL);
}
$isAlive = true;

$lastPing = new Frame(uniqid(), true, Frame::OP_PING);
$this->send($lastPing);

$isAlive = false;
});
}
}

0 comments on commit 8ff3491

Please sign in to comment.