Server Query API для мониторинг-ботов
Готовые сервисы мониторинга показывают «сервер онлайн» зелёной галочкой — и на этом их полезность заканчивается. А вам нужно показать в Discord-канале сообщества, кто сейчас на карте, воткнуть виджет с онлайном на свой сайт или триггерить алерт именно по тем условиям, которые важны вам, а не абстрактному сервису мониторинга. Тут готовые решения кончаются, и начинается свой код поверх query-протокола сервера — это проще, чем кажется, если понимать, как оно устроено под капотом.
Содержание
- Зачем свой инструмент, если есть готовые сервисы
- Базовый принцип: один UDP-пакет туда, один обратно
- Какие протоколы встречаются под разными играми
- Готовые библиотеки вместо парсинга байт руками
- Частота опроса: не долбите сервер каждую секунду
- Практический скелет: от запроса до готового статуса
- Куда встраивать: сайт, бот, внутренние алерты
Зачем свой инструмент, если есть готовые сервисы
Сервисы вроде 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-портом, который нужно пробрасывать отдельно.