|
2 | 2 | namespace Ratchet\Client;
|
3 | 3 | use Evenement\EventEmitterTrait;
|
4 | 4 | use Evenement\EventEmitterInterface;
|
| 5 | +use React\EventLoop\LoopInterface; |
5 | 6 | use React\Socket\ConnectionInterface;
|
6 | 7 | use Psr\Http\Message\RequestInterface;
|
7 | 8 | use Psr\Http\Message\ResponseInterface;
|
@@ -35,6 +36,10 @@ class WebSocket implements EventEmitterInterface {
|
35 | 36 | * @var \Closure
|
36 | 37 | */
|
37 | 38 | protected $_close;
|
| 39 | + /** |
| 40 | + * @var callable |
| 41 | + */ |
| 42 | + private $pongReceiver; |
38 | 43 |
|
39 | 44 | /**
|
40 | 45 | * WebSocket constructor.
|
@@ -92,6 +97,10 @@ function(FrameInterface $frame) use (&$streamer) {
|
92 | 97 | $this->emit('ping', [$frame, $this]);
|
93 | 98 | return $this->send($streamer->newFrame($frame->getPayload(), true, Frame::OP_PONG));
|
94 | 99 | case Frame::OP_PONG:
|
| 100 | + if ($this->pongReceiver) { |
| 101 | + $pongReceiver = $this->pongReceiver; |
| 102 | + $pongReceiver($frame, $this); |
| 103 | + } |
95 | 104 | return $this->emit('pong', [$frame, $this]);
|
96 | 105 | default:
|
97 | 106 | return $this->close(Frame::CLOSE_PROTOCOL);
|
@@ -154,4 +163,41 @@ public function resume()
|
154 | 163 | {
|
155 | 164 | $this->_stream->resume();
|
156 | 165 | }
|
| 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 | + } |
157 | 203 | }
|
0 commit comments