O maior portal de MU Online do Brasil — desde 2003
Tutorial Intermediário Website

Como criar uma página de status (online/offline) do servidor de MU

Aprenda a construir uma página de status que mostra em tempo real se o ConnectServer, GameServer e banco de dados do seu MU Online estão online, com contagem de jogadores e histórico de uptime.

GA Gabriel · Atualizado em 20 jun 2025 · ⏱ 22 min de leitura
Resposta rápida

Uma página de status é um dos elementos que mais transmite confiança em um servidor de MU Online. O jogador quer saber, antes de baixar o cliente, se o servidor está no ar, quantas pessoas estão jogando e se houve quedas recentes. Neste tutorial você vai montar uma página completa em PHP que testa a

Uma página de status é um dos elementos que mais transmite confiança em um servidor de MU Online. O jogador quer saber, antes de baixar o cliente, se o servidor está no ar, quantas pessoas estão jogando e se houve quedas recentes. Neste tutorial você vai montar uma página completa em PHP que testa as portas do ConnectServer e do GameServer via TCP, consulta o banco para contar jogadores online e exibe tudo em um painel com auto-refresh e histórico de uptime. Todo o código é comentado e os valores concretos (portas, nomes de tabela, campos) aparecem como exemplo, pois variam por versão.

Pré-requisitos

Antes de começar, tenha o ambiente montado. Se você ainda não tem o servidor rodando, veja primeiro como criar um servidor de MU Online e depois volte para esta parte web.

RequisitoDetalhe (exemplo — varia por versão)
Servidor webApache ou Nginx com PHP 7.4+ (idealmente 8.1+)
Extensão de bancosqlsrv (SQL Server) ou mysqli conforme sua distro
Acesso às portasConnectServer (ex.: 44405) e GameServer (ex.: 55901) alcançáveis
Credenciais do bancoUsuário com permissão de leitura na base do MU
FirewallPortas de checagem liberadas do host do site para o do jogo

O ponto crítico é o acesso de rede. Se o site está no mesmo VPS do servidor, você testa 127.0.0.1. Se está em um host separado, precisa liberar as portas no firewall do servidor de jogo para o IP do site. Nunca deixe as portas administrativas totalmente abertas para a internet só por causa da página de status — restrinja por IP de origem.

Como funciona a detecção de status

Existem três camadas que valem a pena monitorar, e cada uma responde a uma pergunta diferente:

  1. ConnectServer online? — é o servidor que o cliente contata primeiro para listar os subservers. Se ele cai, ninguém entra. Testamos abrindo um socket TCP na porta dele.
  2. GameServer online? — é onde o jogo de fato roda. Pode haver mais de um (subservers). Testamos a porta de cada um.
  3. Banco de dados respondendo? — se o SQL Server ou MySQL cai, o login falha mesmo com os servidores no ar. Testamos com uma query trivial.

A técnica principal é o teste de porta TCP: tentamos abrir uma conexão. Se ela abre, o serviço está escutando (online). Se recusa ou expira, está offline. Isso não valida o protocolo do MU em si, mas na prática é um indicador excelente e barato.

Passo 1 — Função que testa uma porta TCP

Crie um arquivo status/checker.php. A função central usa fsockopen com timeout curto para nunca travar a página:

<?php
// status/checker.php

/**
 * Testa se uma porta TCP está aceitando conexões.
 * Retorna true (online) ou false (offline).
 */
function portaOnline(string $host, int $porta, float $timeout = 2.0): bool {
    $errno = 0;
    $errstr = '';

    // @ suprime o warning quando a conexão é recusada — tratamos o retorno.
    $conn = @fsockopen($host, $porta, $errno, $errstr, $timeout);

    if ($conn === false) {
        return false; // recusada, filtrada ou timeout = offline
    }

    fclose($conn);
    return true;
}

/**
 * Versão com medição de latência (ms) — útil para exibir "ping".
 */
function checarServico(string $host, int $porta, float $timeout = 2.0): array {
    $inicio = microtime(true);
    $online = portaOnline($host, $porta, $timeout);
    $latencia = (int) round((microtime(true) - $inicio) * 1000);

    return [
        'online'   => $online,
        'latencia' => $online ? $latencia : null,
    ];
}

O parâmetro timeout é o que impede a página de congelar. Com 2 segundos, mesmo se três serviços estiverem offline, o pior caso é 6 segundos — e com cache isso vira quase instantâneo.

Passo 2 — Definir os serviços monitorados

Centralize a configuração dos serviços em um array. Assim, quando você adicionar um subserver novo, edita só um lugar:

<?php
// status/config.php

// ATENÇÃO: portas e IPs abaixo são EXEMPLO — variam por versão e por setup.
return [
    'servicos' => [
        'connect' => [
            'nome'    => 'Connect Server',
            'host'    => '127.0.0.1',
            'porta'   => 44405,          // confira no ConnectServer.ini
        ],
        'game_1' => [
            'nome'    => 'Game Server (Sub 1)',
            'host'    => '127.0.0.1',
            'porta'   => 55901,          // confira no GameServer.ini
        ],
        'game_2' => [
            'nome'    => 'Game Server (Sub 2)',
            'host'    => '127.0.0.1',
            'porta'   => 55902,
        ],
    ],
    'cache_ttl' => 20,                    // segundos de cache do status
];

Passo 3 — Contar jogadores online no banco

O teste de porta diz se o serviço vive, mas o jogador quer ver quantas pessoas estão jogando. Isso vem do banco. Em bases comuns de MU (Season 6 e derivadas), a tabela de personagens ou de contas tem um campo de status de conexão. O nome varia — pode ser ConnectStat, OnlineStatus ou uma tabela MEMB_STAT separada — então ajuste ao seu schema.

<?php
// status/jogadores.php

require_once __DIR__ . '/db.php'; // sua função de conexão (getMuDB)

/**
 * Conta jogadores online lendo o campo de status de conexão.
 * O nome do campo/tabela VARIA POR VERSÃO — ajuste conforme seu schema.
 */
function contarOnline(): ?int {
    try {
        $conn = getMuDB();
    } catch (\Throwable $e) {
        return null; // banco offline
    }

    // Exemplo SQL Server (Character.ConnectStat = 1 significa online)
    $sql = "SELECT COUNT(*) AS total FROM Character WHERE ConnectStat = 1";

    $stmt = sqlsrv_query($conn, $sql);
    if ($stmt === false) {
        return null;
    }

    $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
    return (int) ($row['total'] ?? 0);
}

Em alguns setups, a contagem por ConnectStat pode ficar "presa" após uma queda do GameServer (personagens marcados como online que na verdade caíram). Se isso acontecer no seu servidor, prefira ler de uma tabela de status atualizada pelo próprio GameServer, ou zere o campo no startup do servidor. Documente qual método você usou.

Aqui está o SQL equivalente para quem usa MySQL/MariaDB, apenas trocando a sintaxe:

-- MySQL / MariaDB: contar personagens online
-- Campo de exemplo — confirme o nome no seu banco
SELECT COUNT(*) AS total
FROM Character
WHERE ConnectStat = 1;

-- Alternativa: total de contas com sessão ativa numa tabela de status
SELECT COUNT(*) AS total
FROM MEMB_STAT
WHERE ConnectStat = 1;

Passo 4 — Montar o snapshot com cache

Checar as portas e o banco a cada visita é desperdício e pode sobrecarregar o GameServer se a página viralizar. Guarde o resultado em um arquivo de cache com TTL curto:

<?php
// status/snapshot.php

require_once __DIR__ . '/checker.php';
require_once __DIR__ . '/jogadores.php';

function gerarSnapshot(): array {
    $cfg = require __DIR__ . '/config.php';
    $servicos = [];
    $algumGameOnline = false;

    foreach ($cfg['servicos'] as $chave => $svc) {
        $res = checarServico($svc['host'], $svc['porta']);
        $servicos[$chave] = [
            'nome'     => $svc['nome'],
            'online'   => $res['online'],
            'latencia' => $res['latencia'],
        ];
        if ($res['online'] && str_starts_with($chave, 'game')) {
            $algumGameOnline = true;
        }
    }

    return [
        'atualizado' => date('c'),
        'servicos'   => $servicos,
        // Só conta jogadores se pelo menos um GameServer respondeu.
        'online'     => $algumGameOnline ? (contarOnline() ?? 0) : 0,
        'db_ok'      => contarOnline() !== null,
    ];
}

/**
 * Retorna o snapshot do cache, ou gera um novo se expirou.
 */
function getStatus(): array {
    $cfg = require __DIR__ . '/config.php';
    $cacheFile = sys_get_temp_dir() . '/mu_status.json';
    $ttl = $cfg['cache_ttl'];

    if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
        $dados = json_decode(file_get_contents($cacheFile), true);
        if (is_array($dados)) {
            return $dados;
        }
    }

    $snapshot = gerarSnapshot();
    file_put_contents($cacheFile, json_encode($snapshot));
    return $snapshot;
}

Passo 5 — Endpoint JSON para o front-end

Para que o painel atualize sem recarregar a página inteira, exponha o status como JSON:

<?php
// status/api.php
require_once __DIR__ . '/snapshot.php';

header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');

echo json_encode(getStatus(), JSON_UNESCAPED_UNICODE);

Passo 6 — Página HTML com auto-refresh

Agora a interface. Este trecho consome o api.php e atualiza os indicadores a cada 30 segundos, sem recarregar a página:

<!-- status/index.html -->
<div id="status-painel" class="status-painel">
  <div class="status-header">
    <span id="status-geral" class="badge">Verificando...</span>
    <span id="status-online">— jogadores online</span>
  </div>
  <ul id="status-lista"></ul>
  <small id="status-hora"></small>
</div>

<script>
async function atualizarStatus() {
  try {
    const r = await fetch('/status/api.php', { cache: 'no-store' });
    const d = await r.json();

    // Indicador geral: online se qualquer game server responder
    const algumOnline = Object.values(d.servicos).some(s => s.online);
    const geral = document.getElementById('status-geral');
    geral.textContent = algumOnline ? 'ONLINE' : 'OFFLINE';
    geral.className = 'badge ' + (algumOnline ? 'on' : 'off');

    document.getElementById('status-online').textContent =
      d.online + ' jogadores online';

    // Lista de serviços
    const lista = document.getElementById('status-lista');
    lista.innerHTML = '';
    for (const chave in d.servicos) {
      const s = d.servicos[chave];
      const li = document.createElement('li');
      const ping = s.online ? ` (${s.latencia} ms)` : '';
      li.innerHTML = `<span class="dot ${s.online ? 'on' : 'off'}"></span>
                      ${s.nome}: <strong>${s.online ? 'Online' : 'Offline'}</strong>${ping}`;
      lista.appendChild(li);
    }

    document.getElementById('status-hora').textContent =
      'Atualizado: ' + new Date(d.atualizado).toLocaleTimeString('pt-BR');
  } catch (e) {
    document.getElementById('status-geral').textContent = 'ERRO';
  }
}

atualizarStatus();
setInterval(atualizarStatus, 30000); // a cada 30s
</script>

Passo 7 — Estilo dos indicadores

Um pouco de CSS deixa o painel legível de relance. O ponto verde/vermelho é o que o olho procura:

.status-painel { max-width: 420px; font-family: system-ui, sans-serif; }
.badge { padding: 3px 10px; border-radius: 4px; font-weight: 700; }
.badge.on  { background: #113d1a; color: #35d05a; }
.badge.off { background: #3d1111; color: #ff5c5c; }
.status-lista, #status-lista { list-style: none; padding: 0; }
#status-lista li { padding: 6px 0; display: flex; align-items: center; gap: 8px; }
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.dot.on  { background: #35d05a; box-shadow: 0 0 6px #35d05a; }
.dot.off { background: #ff5c5c; }

Passo 8 — Histórico de uptime (opcional, mas recomendado)

Um snapshot mostra o "agora". Para exibir "99,3% de uptime nos últimos 7 dias", grave o resultado periodicamente numa tabela. Configure um cron a cada minuto chamando um script gravador:

<?php
// status/gravar-uptime.php  (rode via cron a cada 1 minuto)
require_once __DIR__ . '/snapshot.php';
require_once __DIR__ . '/db.php';

$s = gerarSnapshot();
$algumOnline = false;
foreach ($s['servicos'] as $svc) {
    if ($svc['online']) { $algumOnline = true; break; }
}

$conn = getMuDB();
sqlsrv_query($conn,
    "INSERT INTO StatusHistorico (checado_em, online, jogadores) VALUES (GETDATE(), ?, ?)",
    [[$algumOnline ? 1 : 0, SQLSRV_PARAM_IN], [$s['online'], SQLSRV_PARAM_IN]]
);
-- Tabela para o histórico (SQL Server)
CREATE TABLE StatusHistorico (
    id         INT IDENTITY(1,1) PRIMARY KEY,
    checado_em DATETIME NOT NULL,
    online     BIT      NOT NULL,
    jogadores  INT      NOT NULL DEFAULT 0
);
GO

-- Uptime dos últimos 7 dias (percentual)
SELECT
    CAST(SUM(CASE WHEN online = 1 THEN 1 ELSE 0 END) AS FLOAT)
        / COUNT(*) * 100 AS uptime_pct
FROM StatusHistorico
WHERE checado_em >= DATEADD(DAY, -7, GETDATE());

No cron do Linux, a linha seria * * * * * php /var/www/site/status/gravar-uptime.php. No Windows, use o Agendador de Tarefas apontando para o php.exe com o caminho do script.

Erros comuns e soluções

SintomaCausa provávelSolução
Página trava por vários segundosfsockopen sem timeout ou timeout altoUse timeout de 2–3s; ative o cache do snapshot
Sempre mostra offline mesmo com servidor no arFirewall bloqueando a porta do host do siteLibere a porta para o IP de origem do site
Mostra online mas 0 jogadoresCampo de status de conexão diferente do usadoAjuste o nome da tabela/campo ao seu schema
Contagem "presa" após quedaGameServer não zerou ConnectStat no startupZere o campo no boot ou use tabela de status própria
sqlsrv_connect falha intermitentePágina checando o banco a cada requestAtive cache; trate a exceção e mostre "DB offline"
Latência sempre altaResolução de DNS no hostUse IP direto em vez de hostname no checker

Segurança e boas práticas

  • Nunca exponha credenciais no JSON público. O api.php só deve devolver status e contagem, jamais strings de conexão ou detalhes internos.
  • Restrinja as portas por IP. A porta administrativa do servidor não deve ficar aberta ao mundo só para a página funcionar — libere apenas para o IP do site.
  • Trate o banco offline com elegância. Se getMuDB() lançar exceção, mostre "Banco em manutenção" em vez de um erro fatal do PHP que vaza caminhos do servidor.
  • Cache é obrigatório em produção. Sem ele, um pico de acessos vira um pico de conexões TCP e queries no seu GameServer.
  • Evite falso positivo. Uma porta aberta não garante que o login funciona. Se puder, combine o teste de porta com a checagem do banco para um status "saudável" mais honesto.

Checklist de lançamento

  • Portas do ConnectServer e GameServer confirmadas nos .ini
  • Firewall liberando as portas do host do site para o do jogo
  • Função de teste de porta com timeout de 2–3 segundos
  • Contagem de jogadores validada contra o schema real do banco
  • Cache do snapshot ativo (TTL 15–30s)
  • Endpoint api.php retornando JSON sem dados sensíveis
  • Auto-refresh no front-end (30–60s)
  • Tratamento gracioso para banco offline
  • Cron gravando histórico de uptime a cada minuto
  • Teste real: derrubar o GameServer e confirmar que vira "Offline"
  • Teste real: subir de volta e confirmar retorno a "Online"

Com esses passos, sua página de status vira uma vitrine confiável do servidor: rápida, resistente a quedas e honesta sobre o estado real de cada serviço. É o tipo de detalhe que separa um projeto amador de um servidor que os jogadores levam a sério.

Perguntas frequentes

A página de status precisa rodar no mesmo servidor do jogo?

Não obrigatoriamente. Ela pode rodar em qualquer host que consiga alcançar as portas do ConnectServer e GameServer por TCP, ou ler o banco. Muitos admins hospedam o site num VPS separado e monitoram o servidor de jogo pelo IP público — só é preciso liberar as portas no firewall.

Como sei qual porta verificar para o status online?

A porta padrão do ConnectServer costuma ser 44405 e a do GameServer 55901, mas isso varia por versão e por como você configurou o ConnectServer.ini e o GameServer.ini. Confirme sempre nos seus arquivos de configuração antes de codar o checker.

O teste de porta trava a página quando o servidor está offline?

Trava se você não definir timeout. Sempre use um timeout curto (2 a 3 segundos) no fsockopen ou stream_socket_client. Sem timeout, uma porta filtrada pelo firewall pode deixar a requisição pendurada por 30 segundos ou mais.

Dá para contar jogadores online sem acessar a memória do servidor?

Sim. A forma mais estável é contar registros na tabela de personagens ou contas com o campo de status de conexão marcado como online (ConnectStat=1 ou similar). O nome exato do campo varia por versão do MuServer/IGCN/Season.

Com que frequência devo atualizar o status?

Para o visitante, um auto-refresh a cada 30 a 60 segundos é suficiente. No backend, use cache de 15 a 30 segundos para não checar as portas a cada request e não sobrecarregar o GameServer com consultas.

GA
Editor de guias e builds

Gabriel cobre gameplay, builds de classes, PvP e progressão. Testa cada estratégia em servidor antes de publicar.

Continue lendo

Artigos relacionados