Skip to content

Commit bf3f2b0

Browse files
committed
Added the ability to set up a keep alive interval that will close the connection if it does not receive a reply in time.
1 parent f8d9605 commit bf3f2b0

File tree

1 file changed

+46
-0
lines changed

1 file changed

+46
-0
lines changed

src/WebSocket.php

+46
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace Ratchet\Client;
33
use Evenement\EventEmitterTrait;
44
use Evenement\EventEmitterInterface;
5+
use React\EventLoop\LoopInterface;
56
use React\Socket\ConnectionInterface;
67
use Psr\Http\Message\RequestInterface;
78
use Psr\Http\Message\ResponseInterface;
@@ -35,6 +36,10 @@ class WebSocket implements EventEmitterInterface {
3536
* @var \Closure
3637
*/
3738
protected $_close;
39+
/**
40+
* @var callable
41+
*/
42+
private $pongReceiver;
3843

3944
/**
4045
* WebSocket constructor.
@@ -92,6 +97,10 @@ function(FrameInterface $frame) use (&$streamer) {
9297
$this->emit('ping', [$frame, $this]);
9398
return $this->send($streamer->newFrame($frame->getPayload(), true, Frame::OP_PONG));
9499
case Frame::OP_PONG:
100+
if ($this->pongReceiver) {
101+
$pongReceiver = $this->pongReceiver;
102+
$pongReceiver($frame, $this);
103+
}
95104
return $this->emit('pong', [$frame, $this]);
96105
default:
97106
return $this->close(Frame::CLOSE_PROTOCOL);
@@ -154,4 +163,41 @@ public function resume()
154163
{
155164
$this->_stream->resume();
156165
}
166+
167+
/**
168+
* Add a timer to ping the server at a regular interval.
169+
*
170+
* For connections that mostly receive data, it can take a lot of time before the connection is determined to be
171+
* silently gone (e.g. due to connectivity issues). With this method, this check can be made easier.
172+
*
173+
* A ping frame is sent at the interval, and if the corresponding pong is not received by the time the next ping
174+
* is scheduled for, the connection is deemed dead, and is closed.
175+
*
176+
* @param LoopInterface $loop The loop to tie the timer to.
177+
* @param int|float $interval The interval at which to trigger the timer, in seconds.
178+
* @return \React\EventLoop\TimerInterface The periodic timer that is tied to the loop given.
179+
* This allows the caller to cancel the timer later.
180+
*/
181+
public function enableKeepAlive(LoopInterface $loop, $interval = 30)
182+
{
183+
$lastPing = new Frame(uniqid(), true, Frame::OP_PING);
184+
$isAlive = true;
185+
186+
$this->pongReceiver = static function(FrameInterface $frame, $wsConn) use (&$isAlive, &$lastPing) {
187+
if ($frame->getPayload() === $lastPing->getPayload()) {
188+
$isAlive = true;
189+
}
190+
};
191+
return $loop->addPeriodicTimer($interval, function() use (&$isAlive, &$lastPing) {
192+
if (!$isAlive) {
193+
$this->close(Frame::CLOSE_ABNORMAL);
194+
}
195+
$isAlive = true;
196+
197+
$lastPing = new Frame(uniqid(), true, Frame::OP_PING);
198+
$this->send($lastPing);
199+
200+
$isAlive = false;
201+
});
202+
}
157203
}

0 commit comments

Comments
 (0)