Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep alive support #140

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});
}
}