Архитектура мультиплеерного сервера: authoritative vs peer‑to‑peer

Как выбрать между authoritative и P2P для вашей игры: архитектуры, сетевые модели, античит, стоимость и подводные камни продакшена. Практический разбор от команды, которая это строит.
Ошибиться с сетевой архитектурой — значит переписать половину игры за 3 месяца до релиза и потерять маркетинговое окно. Эта статья нужна сейчас, потому что в 2026‑м большинство игровых жанров ожидаемо онлайн, а риск «не угадать» с мультиплеером дороже, чем когда‑либо.
Если коротко: authoritative (сервер — источник истины) выбирают для соревновательных и фритуплей проектов с экономикой и античитом. P2P (игроки синхронизируются между собой) — для коопов «на друзей», ретро/инди с фиксированной физикой и минимальным бюджетом. Дальше — конкретные архитектуры, сетевые модели, стоимость владения и то, что ломается в продакшене.
Когда какой подход разумнее
- Если в игре есть матчмейкинг, ранки, экономика, косметика, сезонные события, внутриигровой магазин — authoritative. Античит и целостность данных важнее, чем экономия на серверах.
- Если это кооператив до 4–8 игроков без экономики, локальные или «друзья по Стиму», физика детерминирована — P2P реальный кандидат. Особенно при желании нулевой серверной стоимости.
- Гибриды возможны: P2P для симуляции боя + облачный релей/сервис аутентификации/статистики; или authoritative‑ядро + клиентская интерполяция и «щедрая» предсказательная логика.
Один прагматичный фильтр: если вы планируете банить читеров — это почти автоматический выбор authoritative. Если вас устраивает «домашний кооп» и вы готовы жить с редкими десинками — рассмотрите P2P.
Архитектурные паттерны
Authoritative: сервер решает все важное
Компоненты:
- Matchmaker (PlayFab, Epic Online Services, custom на Go/Node/Rust).
- Game server (stateful процесс): симулирует мир с фиксированным тикрейтом (30–60 тиков/с), принимает
inputsот клиентов, рассылает state deltas. - Persistence (Postgres/MySQL) для профилей, инвентаря, матч‑результатов.
- Gateway/Relay: UDP‑прокси, WebSocket/QUIC шлюзы (Cloudflare Spectrum, Agones + Kubernetes NodePort/Direct Server Return).
- Античит: на стороне клиента (Easy Anti‑Cheat/BattleEye) + серверные валидации.
Поток:
- Клиент подключается к lobby/matchmaker, получает адрес сервера матча и токен.
- Устанавливается сессия (UDP/QUIC/WebSocket). Клиент шлет инкрементально нумерованные
input‑пакеты. - Сервер применяет входы к состоянию, симулирует, отправляет снапшоты/дельты +
ackпоследнего обработанного инпута. - Клиент делает предсказание (client‑side prediction) и ре-консилиацию по
ack.
Плюсы: честность игры, гибкая телеметрия, контроль метаэкономики. Минусы: стоимость инфраструктуры, DevOps, DDoS‑поверхность.
Инструменты: Nakama (Go, authoritative расширения на Lua/Go), Photon Server/Quantum (C#), Colyseus (Node.js), custom на Rust/Go/C++, контейнеризация (Kubernetes + Agones), фронт Cloudflare/ENet/QUIC.
P2P: игроки симулируют мир сами
Варианты:
- Lockstep (детерминированная симуляция, RTS‑классика): каждый кадр — набор команд, переход к следующему кадру только когда все получили команды текущего.
- Rollback (GGPO‑подход для файтингов/экшенов): клиенты «угадывают» входы, при опоздании — откатывают состояние на n кадров и пересчитывают.
Сетевое:
- STUN/TURN для NAT‑траверсала (WebRTC DataChannels или собственный UDP/ENet).
- Релейный сервер для случаев строгих NAT (вы всё равно платите немного за трафик).
- Хост‑пир как «квазисервер»: один из игроков объявляется авторитетом по некоторым подсистемам (спавн, лут), но без центрального дата‑хранилища.
Плюсы: минимальная стоимость, низкая задержка при локальной игре. Минусы: сложность детерминизма (разные CPU/GPU/платформы), уязвимость к читам, больная отладка десинков.
Сетевые модели и синхронизация
Клиентское предсказание и согласование с сервером
Стандарт для authoritative. Клиент представляет, что его действие уже применено, чтобы скрыть лаг, а затем подгоняет локальное состояние под серверный «истинный» результат.
Пример (Unity/C#):
public class PlayerNetworkController : MonoBehaviour {
struct InputCmd { public int seq; public Vector2 move; public float ts; }
struct State { public Vector3 pos; public Vector3 vel; public int ackSeq; }
Queue<InputCmd> pending = new Queue<InputCmd>();
int nextSeq = 1;
Rigidbody rb;
void Start() { rb = GetComponent<Rigidbody>(); }
void Update() {
var input = new InputCmd {
seq = nextSeq++,
move = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")),
ts = Time.time
};
SendToServer(input); // UDP/QUIC/WebSocket
pending.Enqueue(input);
ApplyInputLocal(input); // prediction
}
void OnServerSnapshot(State s) {
// жестко ставим авторитетное состояние
rb.position = s.pos; rb.velocity = s.vel;
// выбрасываем подтвержденные инпуты
while (pending.Count > 0 && pending.Peek().seq <= s.ackSeq) pending.Dequeue();
// переигрываем оставшиеся, чтобы выровнять предсказание
foreach (var cmd in pending) ApplyInputLocal(cmd);
}
void ApplyInputLocal(InputCmd cmd) {
var dir = new Vector3(cmd.move.x, 0, cmd.move.y).normalized;
rb.AddForce(dir * 10f, ForceMode.Acceleration);
}
}
На сервере — симуляция с фиксированным tickRate и буфер входов по seq. Клиент в идеале хранит 100–200 мс необработанных инпутов. Лирическое отступление: если хочется простоты — сделайте однопользовательскую игру; сеть добавит еще одну ось сложности.
Lockstep и детерминизм
Lockstep требует бит‑в‑бит одинаковой симуляции. Практически:
- Фиксированная точность чисел (int/fixed‑point вместо float где возможно).
- Фиксированный порядок итерации по коллекциям (не полагайтесь на
HashMap/Dictionaryпорядок). - Одинаковое время кадра (fixed delta, не привязывать логику к
Time.deltaTime). - Синхронизация случайности (общие seed’ы RNG).
Rollback и «перемотка» времени
Rollback держит историю N последних тиков (например, 8–12), хранит состояние (или детерминированные входы) и при приходе позднего ввода пересчитывает состояние. Важно:
- Быстрая сериализация состояния (битовые буферы, Arena‑аллокаторы, ECS без боковых эффектов).
- Timewarp для анимаций/частиц (визуал отдельно от логики).
- Бюджет CPU/GPU на пересчет (особенно на консолях/мобайле).
Сравнение authoritative vs P2P
| Критерий | Authoritative | P2P |
|---|---|---|
| Читероустойчивость | Высокая: сервер валидирует и симулирует | Низкая/средняя: валидировать сложно |
| Стоимость инфраструктуры | Средняя/высокая: сервера матчей, DevOps | Низкая: STUN/релей по необходимости |
| Задержка восприятия | Скрывается предсказанием/интерполяцией | Может быть ниже локально, но десинки |
| Сложность реализации | Высокая: симуляция, согласование, античит | Высокая: детерминизм, rollback/lockstep |
| Масштабируемость по CCU | Отличная при оркестрации (K8s/Agones) | Ограничена релеями/NAT и жанром |
| Размер сессии | 10–200+ (шардинг) | Обычно 2–8, реже 16 |
| Сохранность экономики | Гарантируется на сервере | Риск дублирования/эксплойтов |
| Отладка | Центральные логи/трейсы | Распределенная, сложнее воспроизводить |
Масштабирование и эксплуатация
Тик‑бюджет и CPU
- 60 тиков/с — 16.67 мс на тик, включая сетевой I/O, логику, сериализацию. На 30 тиков — 33.3 мс, запас выше, но увеличивается видимая задержка и требования к интерполяции.
- Выделяйте бюджет на кодек: дельты по компонентам, bit‑packing, компрессия (LZ4/S2). Для мобильной сети это критично.
Оркестрация и автоскейл
- Kubernetes + Agones для пулов игровых серверов, pre‑warm нод под прайм‑тайм.
- Подбор региона: latency‑aware матчмейкинг. Near‑edge релеинг (Cloudflare Spectrum/Workers) для «лестницы» NAT.
- Кеши/метаданные: Redis для матчей/серверного discovery, Postgres для долговременных профилей.
Сетевые протоколы
- UDP + собственная надстройка (reliability, ordering) — классика. ENet — зрелая библиотека.
- QUIC (HTTP/3) — альтернатива: встроенная надёжность/шифрование, лучше сквозь NAT/фаерволы, но добавляет накладные расходы.
- WebRTC DataChannels — must для браузера; получаете STUN/TURN «из коробки».
Телеметрия
- Пер‑тик метрики: «сколько инпутов потеряно», jitter, RTT, размер пакета. Старайтесь не писать «всё в Grafana» без агрегации — храните агрегаты по матчам/региону.
- Реплеи как диагностический артефакт: на authoritative — лог входов/снапшотов; на P2P — входы + seed’ы.
Что ломается в продакшене
- Детерминизм на разных платформах: ARM vs x86, разная реализация FPU, компиляторные оптимизации. Лечится фиксированной арифметикой и тест‑фермой «скрещенных» клиентов.
- NAT hairpin и симметричные NAT: P2P ломается без релея. Заложите TURN/relay бюджет и health‑чек на деградацию до релея.
- Спайки GC/аллокации: C# на сервере без пулов структур быстро ловит паузы. Используйте арену, Span/ArrayPool, ECS без боковых эффектов.
- Дублирование пакетов и reorder: ваш протокол обязан иметь
seq/ack/bitmaskи адаптивный resend. Не надеяться на «повезёт». - Время: рассинхронизация часов устройства. Не используйте
DateTime.Nowдля логики — только серверный тик/монотонные таймеры. - DDoS/abuse: UDP amplification, SYN‑флуды на гейтвей. Ставьте rate limiting на уровне edge, обрезайте «подозрительные» IP‑диапазоны, следите за уязвимостями прокси (см. наш разбор новой уязвимости Nginx).
- Версионирование протокола: выкатили патч — половина клиентов старые. Придётся держать 2–3 версии сериализации и миграцию состояния при реконнекте.
- Читеры: aimbot, speedhack, packet injection. Сервер валидирует скорость/ускорение, «вес» коллизий и частоту выстрелов; подозрительные паттерны — в карантин.
Деньги и сроки: сколько стоит и зачем оно вам
Сильные хотелки обычно разбиваются о бюджет. Ориентиры по рынку и опыту студий:
- Прототип P2P коопа (2–4 игрока, WebRTC/UDP, без экономики): 4–8 недель, бюджет от $20–40k. Основной риск — детерминизм/rollback.
- Минимальный authoritative для сессионного шутера (до 16 игроков, матчмейкинг, базовая прогрессия): 3–5 месяцев, от $80–200k. Риск — инфраструктура/античит.
- МMO‑подсистемы (persistent мир, шардинг, инвентарь, торговля): 6+ месяцев, бюджеты считаются индивидуально.
OPEX:
- Authoritative: при 60 тиках/с и 10–16 игроках в матче типичный серверный процесс укладывается в 0.2–0.6 vCPU и 200–500 МБ RAM. С учётом стоек/релеев/матчмейкера — ориентируйтесь на ~$0.02–0.08 за час матча по инфраструктуре в облаке при средней утилизации. Релеинг через TURN/edge увеличит трафиковую строку.
- P2P: почти ноль без релеев; с TURN — платите за egress/GB и несколько центов за сессию. Экономия часто «съедается» сложностью QA.
Скрытые затраты:
- QA‑матрица платформ/сетей (Wi‑Fi/4G/5G, строгий NAT, консоли). Автоматизация сетевых тестов окупается.
- Туллинг: интеграция реплеев, профайлеров, симуляция packet loss/jitter в CI.
- Аналитика: корреляция лагов и оттока игроков — даёт прямой эффект на удержание и монетизацию.
ROI‑логика:
- Компетитив с монетизацией и сезонностью — платит за authoritative десятикратно через LTV и борьбу с читами.
- Небольшой кооп без экономики — P2P позволит раньше выйти и проверить фитовость геймплея.
Практические решения по жанрам
- Шутер/файтинг/спортивные: authoritative с агрессивной клиентской предикцией, серверной валидацией хитсканов, lag compensation (rewind по input time, храните 100–200 мс истории позиций для трассировки пуль).
- RTS/город‑билдер: P2P lockstep или гибрид; симуляция детерминированна, сетевые пакеты — команды, не состояния.
- Платформер/ретро‑экшен 1v1: rollback (GGPO‑подобная модель), иногда с релеем.
- Sandbox/физика: authoritative симуляция тяжелых взаимодействий, на клиенте — «мягкие» эффекты, которые можно корректировать без боли.
Минимальный каркас authoritative‑сервера (UDP)
// Node.js + dgram (демо-скелет), в продакшене используйте ENet/QUIC и неблокирующие структуры
import dgram from 'dgram';
const TICK = 33; // 30 тиков/с
let state = new Map(); // playerId -> {x,y,vx,vy,ack}
let inputs: Array<{id:string, seq:number, dx:number, dy:number}> = [];
const srv = dgram.createSocket('udp4');
srv.on('message', (msg, rinfo) => {
// parse: id|seq|dx|dy
const [id, s, dx, dy] = msg.toString().split('|');
inputs.push({ id, seq: Number(s), dx: Number(dx), dy: Number(dy) });
// сохраняем адрес пира для обратной отправки (упрощенно)
(state.get(id) || (state.set(id, { x:0, y:0, vx:0, vy:0, ack:0 }), state.get(id))).addr = rinfo;
});
setInterval(() => {
// применяем входы
const byPlayer = new Map<string, Array<any>>();
for (const inp of inputs) {
if (!byPlayer.has(inp.id)) byPlayer.set(inp.id, []);
byPlayer.get(inp.id)!.push(inp);
}
inputs = [];
for (const [id, arr] of byPlayer) {
const s = state.get(id) || { x:0, y:0, vx:0, vy:0, ack:0 };
arr.sort((a,b)=>a.seq-b.seq);
for (const a of arr) {
s.vx = a.dx * 5; s.vy = a.dy * 5; // простая физика
s.x += s.vx * (TICK/1000); s.y += s.vy * (TICK/1000);
s.ack = a.seq;
}
state.set(id, s);
}
// шлем снапшоты (упрощенно — каждому свой ack и весь список игроков)
const snapshot = JSON.stringify([...state.entries()].map(([id, s]) => ({ id, x:s.x, y:s.y })));
for (const [id, s] of state) {
const payload = JSON.stringify({ you:id, ack:s.ack, snapshot });
const addr = (s as any).addr; if (!addr) continue;
srv.send(payload, addr.port, addr.address);
}
}, TICK);
srv.bind(27015);
Это не продакшен, но показывает сердцевину: буфер входов, симуляция по тикам, рассылка снапшотов с ack.
Выбор сериализации и протокола данных
- JSON удобен, но громоздок. Для продакшена — FlatBuffers, Protobuf, custom bit‑packing.
- Схема версионируется:
versionв каждом пакете, fallback‑логика. - Передача позиций — дельты + квантование (например, 1/100 юнита, чтобы упаковать в 16 бит).
Безопасность и честность
- Токены доступа краткоживущие (JWT с audience=matchId). Сервер проверяет
iat/expи подпись (HMAC/EdDSA). - Нормализация скоростей: ограничивайте максимальные векторы, отфильтровывайте «телепорты» без причины.
- Сетевая изоляция: игровые воркеры без доступа в интернет, только через гейтвей.
- Ротация ключей шифрования и античит‑хэндшейк на старте матча.
Организация разработки
- Net‑sim в CI: запуск headless сервера + 4 клиентов с эмуляцией задержки/потерь (
tc netem), проверка средних метрик и десинков. - Рекордер команд/состояний и кнопка «реплей матча» для QA.
- Фичи под флагами/канарейками: выкатывайте новый формат снапшотов 5% игроков в регионе.
FAQ
Можно ли сделать гибрид: P2P геймплей и серверная экономика?
Да. Держите P2P для симуляции боя, а все ценности (прогресс, инвентарь, дроп) фиксируйте на сервере по «резюме матча». Придётся подписывать результаты и валидационно прогонять подозрительные реплеи.
Для браузера лучше WebRTC или WebSocket?
Для реального‑тайма — WebRTC DataChannels: UDP‑семантика, низкая задержка, встроенные STUN/TURN. WebSocket — TCP, больше head‑of‑line блокировок; годится для лобби/экономики, хуже для тиков.
Есть ли смысл в QUIC для настольных игр?
Есть. QUIC лучше проходит сквозь NAT/фаерволы, имеет TLS по умолчанию. Но отдаёт управление над reliability протоколу — если нужна тонкая настройка частично‑надёжных каналов, ENet/UDP иногда удобнее.
Какой тикрейт выбирать?
30 тиков/с — хороший базовый компромисс. 60 — если геймплей быстрый (шутеры/файтинги) и вы уложились в производительность/трафик. Всегда тестируйте с реальными сетями и девайсами.
Как бороться с десинком в P2P?
Строгий детерминизм (fixed‑point), общий RNG, запись входов и периодические контрольные хеши состояния между пирами. При расхождении — мягкий ресинк или переключение на релей/хоста.
Что выбрать для коопа на 4 человека на мобилках?
Если нет экономики — P2P через WebRTC/UDP с обязательным релеем на крайний случай. Если планируете соревновательные элементы/рейтинги — сразу authoritative с UDP/QUIC и строгой валидацией.
Ключевые выводы
- Authoritative нужен там, где важны честность, экономика и масштаб; P2P оправдан при малом бюджете и детерминированной симуляции.
- Реальный‑тайм держится на предсказании, интерполяции и аккуратной сериализации; без этого видна вся сетка.
- Детерминизм — главный риск P2P; античит и инфраструктура — главный риск authoritative.
- Тик‑бюджет, оркестрация и телеметрия — операционный фундамент, без них проект не скейлится.
- Закладывайте релей/edge‑защиту: NAT и DDoS сломают идеальную схему в первый же уикенд.
Если вы строите мультиплеерный шутер, кооп или гибридную архитектуру — мы в MTBYTE проектируем, пишем и запускаем такие серверы под Steam, мобильные, браузер и консолы. Напишите нам через /contact — обсудим требования и риски до кода.