MAATRIX GAMES / Блог / Server Query API для мониторинг-ботов

Server Query API для мониторинг-ботов

MAATRIX GAMES

Готовые сервисы мониторинга показывают «сервер онлайн» зелёной галочкой — и на этом их полезность заканчивается. А вам нужно показать в Discord-канале сообщества, кто сейчас на карте, воткнуть виджет с онлайном на свой сайт или триггерить алерт именно по тем условиям, которые важны вам, а не абстрактному сервису мониторинга. Тут готовые решения кончаются, и начинается свой код поверх query-протокола сервера — это проще, чем кажется, если понимать, как оно устроено под капотом.

Зачем свой инструмент, если есть готовые сервисы

Сервисы вроде UptimeRobot или встроенных чекеров хостинг-панелей отвечают ровно на один вопрос: сервер отвечает на порт или нет. Этого достаточно, если задача — узнать о падении. Но как только нужно что-то чуть сложнее — показать список игроков на текущей карте, посчитать среднюю заполненность сервера за неделю, автоматически написать в Discord «вайп через 10 минут, сейчас на сервере 34 человека» — готовый сервис не про это, он не умеет доставать структурированные данные и тем более не умеет их встраивать туда, куда нужно вам.

Свой инструмент опроса — это, по сути, десяток-другой строк кода, которые раз в некоторое время стучатся на сервер и получают структурированный ответ: карту, число игроков, их ники, версию сервера, пинг. Дальше с этими данными можно делать что угодно: кормить Discord-бота сообщества, который держит канал со статусом сервера в реальном времени, рисовать live-виджет на сайте клана, писать метрики в свою базу для графиков онлайна по дням, или триггерить уведомление, если онлайн внезапно упал до нуля посреди дня — что часто раньше замечает такой самописный бот, чем игрок, который просто закрыл клиент и молча ушёл.

Здесь не будет ни слова про RCON — это другой протокол для отправки команд и требует пароля. Query — read-only, без авторизации, и именно поэтому годится для публичных виджетов и ботов: его не страшно светить наружу, он физически не может ничего сломать на сервере.

Базовый принцип: один UDP-пакет туда, один обратно

Все игровые query-протоколы, которые встречаются на практике, устроены на удивление одинаково. Клиент — ваш бот или скрипт — отправляет короткий UDP-пакет на игровой или отдельный query-порт сервера. Сервер, если он жив и настроен отвечать на такие запросы, присылает пакет назад с бинарной структурой: магический заголовок, дальше поля вроде названия сервера, текущей карты, числа игроков, максимума слотов, версии, иногда — списка ников и их пингов.

Дальше вашей стороне остаётся распарсить этот бинарный ответ по спецификации конкретного протокола: прочитать байты в нужном порядке, с нужной кодировкой строк (обычно UTF-8 или Latin-1 с завершающим нулевым байтом), учесть эндианность чисел. Это самая муторная часть, если писать с нуля — но, как правило, писать с нуля и не приходится, о библиотеках чуть ниже.

Ключевая особенность UDP — отсутствие гарантии доставки. Пакет может не дойти, ответ может потеряться, сервер может быть перегружен и не успеть ответить за разумное время. Поэтому в любом самописном опроснике обязательны: таймаут на ожидание ответа (обычно 1-3 секунды достаточно) и обработка случая «ответа не было» как отдельного состояния, а не как ошибки — сервер вполне может быть жив, просто пакет потерялся, и один пропущенный опрос ничего не значит, если следующий проходит нормально.

Отдельно стоит протокол A2S, на котором построены Source- и Source 2-игры (CS2, Rust, Garry's Mod и другие) — он настолько распространён, что заслуживает отдельного разбора: как устроен рукопожатие с challenge-числом, что происходит с крупными ответами, которые режутся на фрагменты, и какие нюансы вылезают на практике. Всё это подробно разобрано в статье про протокол A2S и запросы статуса сервера — если ваш сервер на Source-движке, читайте её как основной справочник, а здесь считайте A2S одним из частных случаев общей картины.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Какие протоколы встречаются под разными играми

Единого «query-протокола для всех игр» не существует — каждое семейство движков придумало своё, и это первое, что нужно выяснить перед тем, как писать код.

  • Source / Source 2 (CS2, Rust отчасти, Garry's Mod, Team Fortress 2, Left 4 Dead 2 и другие Source-игры) — протокол A2S, запрос идёт на тот же UDP-порт, что и игровое подключение, авторизация не нужна.
  • Rust — помимо совместимости с A2S по игровому порту, у Rust есть отдельный Steam query-порт, задаваемый параметром запуска +server.queryport, который стоит явно прописать в конфиге сервера, если опрашиваете его напрямую, а не полагаетесь на игровой порт.
  • Minecraft (ванильный сервер, Paper, Spigot, Fabric) — два разных механизма. Server List Ping (SLP) встроен в сам игровой протокол поверх TCP того же порта, на котором висит сервер, — именно так клиент Minecraft показывает пинг и онлайн в списке серверов. Отдельно есть Query-протокол в духе GameSpy4 поверх UDP, его нужно явно включить в server.properties: enable-query=true, query.port=25565 (по умолчанию совпадает с игровым портом, но можно развести). Query отдаёт более полные данные — список плагинов, версию, полный список игроков — SLP же компактнее и не требует включения.
  • ARK: Survival Evolved / ARK: Survival Ascended — Steam query поверх UDP на отдельном порту, который задаётся параметром QueryPort= (или ?QueryPort= в командной строке запуска) и по конвенции чаще всего равен игровому порту +1, но это не жёсткое правило — сверяйтесь со своим конфигом.
  • Многие менее крупные и модовые серверы либо реализуют совместимость с A2S, либо документируют собственный протокол в вики движка — в этом случае единственный надёжный источник истины — официальная документация конкретной игры, а не предположения по аналогии.

Прежде чем писать опросник под конкретную игру — проверьте в панели управления или конфиге сервера, какой именно порт отвечает на query-запросы: путаница между игровым портом и отдельным query-портом — самая частая причина, почему «бот не видит сервер», хотя сервер прекрасно работает.

Готовые библиотеки вместо парсинга байт руками

Писать бинарный парсер с нуля для каждого протокола — плохая идея, если только это не учебное упражнение: слишком легко ошибиться в порядке байт или обрезать строку не на том символе. На практике для большинства языков программирования есть готовые библиотеки категории «universal game server query» — они умеют определять протокол по типу игры и отдают уже распарсенный объект с полями онлайна, карты, списка игроков, вместо того чтобы возиться с сырыми байтами.

Для Node.js/JavaScript есть библиотека gamedig, которая поддерживает запросы к десяткам разных движков и игр через единый интерфейс — передаёте тип игры и адрес, получаете JSON с онлайном и списком игроков, без разбора протокола вручную. Для Python отдельно под A2S есть python-a2s, о котором подробно в статье про сам протокол; для других семейств протоколов в экосистеме Python тоже встречаются точечные библиотеки под конкретную игру или движок — стоит поискать по названию протокола перед тем, как писать парсер самому. Для Go, Java и других языков ситуация похожая: под самые массовые протоколы (в первую очередь A2S, как самый распространённый) обычно находится хотя бы одна поддерживаемая библиотека, под менее популярные — иногда приходится писать минимальный парсер самому, ориентируясь на спецификацию протокола.

Если готовой библиотеки под нужный язык и протокол не нашлось, минимальный самописный парсер — это не катастрофа: структура ответа обычно документирована (для A2S — в вики Valve Developer Community, для Minecraft Query — в спецификации GameSpy4), и типичный ответ укладывается в 20-40 строк кода на чтение полей по порядку.

Частота опроса: не долбите сервер каждую секунду

Соблазн опрашивать сервер раз в секунду, чтобы виджет на сайте обновлялся «в реальном времени», встречается у всех новичков в этой теме — и почти всегда это плохая идея. Каждый query-запрос — это нагрузка на сетевой стек сервера и, если игра сама формирует список игроков под каждый запрос (а некоторые так и делают), лишняя работа для игрового процесса, которая конкурирует за ресурсы с самой игровой логикой.

Разумный интервал опроса для большинства задач — от 15 до 60 секунд. Для Discord-статуса или виджета на сайте, где человек и так не смотрит на цифры непрерывно, 30 секунд — комфортный баланс между свежестью данных и нагрузкой. Для uptime-алертов интервал можно сократить, но не за счёт частых прямых query-запросов к каждому серверу — лучше смотреть в сторону отдельного дежурного процесса с собственной логикой ретраев, что подробнее разобрано в статье про uptime-мониторинг игрового сервера.

Если данные нужны сразу в нескольких местах (бот, сайт, внутренний дашборд), не размножайте прямые запросы к серверу под каждое место показа — опрашивайте один раз по расписанию, кэшируйте результат в памяти процесса, файле или простой базе (Redis, SQLite — под задачу), а все потребители пусть читают из кэша. Это и снижает нагрузку на игровой сервер, и развязывает опрос от отображения: сайт может лежать, а последний известный статус всё равно будет показан из кэша, пока опросник продолжает работать в фоне.

Практический скелет: от запроса до готового статуса

Универсальный псевдокод такого воркера выглядит одинаково независимо от протокола и языка — меняется только сама функция запроса:

каждые 30 секунд:
    попытаться:
        ответ = query(host, port, timeout=2s)
        статус = {
            online: true,
            players: ответ.players,
            max_players: ответ.max_players,
            map: ответ.map,
            player_list: ответ.player_list,
            ping_ms: ответ.ping
        }
    поймать таймаут/ошибку:
        статус = { online: false, last_seen: время_последнего_успешного_опроса }

    сохранить статус в кэш (Redis/файл/переменная процесса)
    если статус изменился существенно (онлайн упал до 0, или сервер снова поднялся):
        отправить webhook/сообщение в Discord

На базе Node.js с gamedig внутренний вызов функции query выглядит примерно так — библиотека сама разбирается с протоколом по указанному типу игры:

const { GameDig } = require('gamedig');

async function pollServer(host, port) {
  try {
    const state = await GameDig.query({ type: 'csgo', host, port, socketTimeout: 2000 });
    return { online: true, players: state.players.length, maxplayers: state.maxplayers, map: state.map };
  } catch (e) {
    return { online: false };
  }
}

Дальше этот результат кормит либо REST-эндпоинт, который отдаёт JSON вашему фронтенду для виджета онлайна на сайте, либо напрямую отправляется в Discord через webhook или через уже поднятого бота — тут пригодится связка с логикой из статьи про Discord-бота для управления игровым сервером, где такой опросник дополняет RCON-команды живым статусом прямо в канале сообщества.

Куда встраивать: сайт, бот, внутренние алерты

Три типичных сценария использования такого опросника закрывают почти все реальные запросы админов.

Виджет на сайте клуба или клана. Небольшой бэкенд-эндпоинт отдаёт закэшированный статус в JSON, фронтенд раз в 30 секунд подтягивает его через fetch и обновляет блок «онлайн: 24/64, карта: de_mirage». Прямой запрос из браузера пользователя к игровому серверу по UDP не сделать — браузеры этого не позволяют, — поэтому опрос всегда идёт через ваш бэкенд, а не напрямую с клиента.

Discord-статус сообщества. Бот раз в интервал обновляет своё сообщение в канале или собственный статус активности (playing) числом текущих игроков — это тот самый сценарий, где живой query становится частью уже существующего RCON-бота, а не отдельным проектом с нуля.

Внутренние алерты для админской команды. Если у вас несколько серверов и хочется единой панели, где сразу видно, какой из них просел по онлайну или недоступен — тот же опросник, только пишущий в общую базу или простую систему метрик, откуда потом строится дашборд или уходят уведомления в рабочий чат команды, а не игроков.

Во всех трёх случаях логика опроса одна и та же — разница только в том, куда уходит результат.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Частые вопросы

Query-протокол требует пароль или авторизацию?

Нет, в отличие от RCON query — read-only и не требует никаких учётных данных. Именно поэтому его безопасно светить наружу для публичных ботов и виджетов: максимум, что может получить внешний запрос — публичную информацию о статусе, но не управление сервером.

Почему бот не получает ответ, хотя сервер точно работает?

Чаще всего дело в порте — путают игровой порт с отдельным query-портом (актуально для ARK, Rust с отдельным Steam query-портом), либо query вообще не включён в конфиге (характерно для Minecraft, где enable-query по умолчанию выключен). Также стоит проверить, что файрвол или NAT хостинга не режет входящий UDP на нужный порт.

Можно ли получить список ников игроков, а не только число?

Да, большинство протоколов, включая A2S с полным player-запросом и Minecraft Query, отдают полный список ников вместе с временем на сервере (или пингом) для каждого игрока — это доступно через второй тип запроса поверх базового статуса, обычно с тем же challenge-механизмом, что и основной запрос.

Стоит ли писать свой парсер протокола, если есть библиотеки?

Только если под ваш язык и протокол реально нет поддерживаемой библиотеки, либо задача узкоспециальная (например, нужен доступ к полю, которое библиотека не парсит). В остальных случаях самописный бинарный парсер — источник трудноуловимых багов на граничных случаях (фрагментированные ответы, нестандартная кодировка), которые в готовых библиотеках уже отловлены и починены сообществом.

Что делать, если сервер за NAT или с проброшенным портом через сторонний IP?

Query нужно слать на тот адрес и порт, который реально слушает сервер снаружи — то есть внешний IP с проброшенным портом, а не внутренний адрес контейнера или локальной сети. Если сомневаетесь, тот же адрес и порт, что указываете игрокам для подключения, обычно годится и для query — за исключением случаев с отдельным query-портом, который нужно пробрасывать отдельно.