Heartbeat

Atenção: Aplicações de conexão longa devem adicionar heartbeats, caso contrário, a conexão pode ser forçosamente encerrada pelos nós de roteamento devido à ausência de comunicação por um longo período.

O heartbeat tem duas funções principais:

  1. O cliente envia dados periodicamente ao servidor para evitar que a conexão seja encerrada por um firewall de algum nó devido à falta de comunicação por um longo período.

  2. O servidor pode usar o heartbeat para verificar se o cliente está online. Se o cliente não enviar nenhum dado dentro do tempo estipulado, o servidor considera que o cliente está offline. Isso permite detectar eventos de desconexão extrema do cliente (como falta de energia, queda na internet, etc.).

Intervalo sugerido para o heartbeat:

Sugerimos que o cliente envie o heartbeat com um intervalo inferior a 60 segundos, como por exemplo 55 segundos.

Não há requisitos para o formato dos dados do heartbeat, desde que o servidor consiga reconhecê-lo.

Exemplo de Heartbeat

<?php
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

// Intervalo do heartbeat de 55 segundos
define('HEARTBEAT_TIME', 55);

$worker = new Worker('text://0.0.0.0:1234');

$worker->onMessage = function(TcpConnection $connection, $msg) {
    // Define temporariamente um atributo lastMessageTime para o connection, para registrar o tempo da última mensagem recebida
    $connection->lastMessageTime = time();
    // Outras lógicas de negócio...
};

// Após o processo ser iniciado, define um temporizador que executa a cada 10 segundos
$worker->onWorkerStart = function($worker) {
    Timer::add(10, function()use($worker){
        $time_now = time();
        foreach($worker->connections as $connection) {
            // É possível que a conexão ainda não tenha recebido mensagens, neste caso, define lastMessageTime como o tempo atual
            if (empty($connection->lastMessageTime)) {
                $connection->lastMessageTime = $time_now;
                continue;
            }
            // Se o intervalo desde a última comunicação for maior que o intervalo do heartbeat, considera-se que o cliente está offline e fecha a conexão
            if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
                $connection->close();
            }
        }
    });
};

Worker::runAll();

A configuração acima faz com que, se o cliente não enviar nenhum dado ao servidor por mais de 55 segundos, o servidor considere que o cliente está offline, feche a conexão e acione o evento onClose.

Reconexão após desconexão (Importante)

Independentemente de o cliente ou o servidor enviar heartbeats, a conexão pode ser interrompida. Por exemplo, o JavaScript pode ser pausado quando o navegador é minimizado, quando a aba do navegador é trocada, quando o computador entra em modo de suspensão, quando dispositivos móveis trocam de rede, a sinalização é fraca, o telefone é desligado, o aplicativo do telefone vai para o segundo plano, falhas em roteadores e desconexões proativas de serviços, entre outros. Especialmente em ambientes externos, onde muitos nós de roteamento tendem a limpar conexões inativas por mais de 1 minuto, isso é o que justifica a recomendação de um intervalo de heartbeat inferior a 1 minuto.

As conexões em ambientes externos são facilmente desconectadas, portanto a reconexão após desconexão é uma funcionalidade essencial para aplicações de conexão longa (apenas o cliente pode realizar a reconexão, o servidor não pode implementá-la). Por exemplo, o WebSocket do navegador deve escutar o evento onclose e, ao ocorrer o onclose, estabelecer uma nova conexão (para evitar a necessidade de um colapso, a nova conexão deve ser estabelecida com um atraso). De forma mais rigorosa, o servidor também deve periodicamentem enviar dados de heartbeat, e o cliente deve monitorar se os dados de heartbeat do servidor estão dentro do tempo estipulado. Se o cliente não receber os dados de heartbeat do servidor dentro do tempo definido, a conexão deve ser considerada como encerrada, e deve executar o close para fechar a conexão e estabelecer uma nova conexão.