Разработку сервера часто начинают с экономии байтов: берут UDP ради коротких заголовков, прячут команды в флаги, включают сжатие. А потом выясняется, что один процесс не успевает вычитывать даже десятую часть того, что сетевая карта уже приняла. Оптимизировали не то.

сетевая карта
сетевая карта

Пропускная способность

Когда речь идет про сетевые карты, роутеры и другие устройства для приема и передачи пакетов главным их показателем считается пропускная способность которая часто составляет 100 Мегабит в секунду на домашних устройствах.

Многие ошибочно полагают что зная этот параметр, а так же скорость вашего интернета которая так же выражается в Мегабитах в секунду (Мбит/с) и размер передаваемых пакетов вашему серверу смогут посчитать количество команд (пакетов) игроков в секунду.

Почему экономия байтов не первое дело

Например, многие полагают что при фактической скорости 10 Мб/с интернета и размере пакетов от клиентов 64 байта смогут обработать 1 250 000 таких пакетов в секунду (1 Мбит = 125 000 байт) и в первую очередь стремятся уменьшить размер пакета следующим образом:

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

  • Используют на этапе проектирования нечитабельные (кроме самого их автора) байт - флаги в которых кроется какая-то команда (взамен использования текстовых понятных человеку команд, например в том же json)

  • Использование сжатие (которое тратит время перед отправкой пакетов и тормозит процесс)

Почему всё упирается в процессор

Уменьшение размера пакетов - необходимая часть разработки, но ни как не на этапе проектирования. Однако очень часто разработчика сервера начинается с этого, а любые лишние действия требуют времени, усложняю проект, увеличивают шанс ошибок и приближают эмоциональное выгорание оттягивая релиз вашего проекта для показа другим людям (если мозг долго не получает положительную обратную связь то начинает полагать что занимается ерундой).

Математический вывод выше о количестве обработанных пакетов не является верным - один процесс (поток) не способен считывать с сетевого стека пакеты с той же скоростью с которых сетевое устройство их принимает.

И вот тут и кроется самое основное, что должен предусмотреть разработчик - возможность работы сервера в нескольких параллельных процессах (потоках), а как я рассказал в другой статье за это отвечает количество ядер процессора CPU и их thread (нитей).

Если сервер работающих в один поток не способен прочитать и 10% принятых пакетов (бывает что и 1%), то уменьшение размера пакетов (которые в играх обычно небольшие) даст лишь небольшой прирост в скорости и это выглядит не первоочередной задачей, а скорее той, которую нужно решать в самую последнюю очередь для оптимизации и уменьшения количества ядер CPU .

Задержка сетевого устройства

Для определения скорости с которой сетевое устройство может передать пакет дальше производители этих устройств публикуют в тестах выполняемых стандартом под названием RFC 2544 в разделе Latency (Frame Latency). Иногда можно встретить упоминание RFC 1242 на котором основаны методы замера.

Ниже представлена выдержка из инструкции тест-анализатора сетевого оборудования, что означает показатель Latency (задержки)

фото из инструкции Беркут-ET http://metrotek.center/files/doc/all/b3et-ug_1.2.2_ru.pdf
фото из инструкции Беркут-ET http://metrotek.center/files/doc/all/b3et-ug_1.2.2_ru.pdf

Однако с сетевыми устройствами можно работать в параллельных потоках и т.к. "под капотом" оно работает параллельно (на своих встроенных ядрах и нитях thread) и, как я полагаю, асинхронно - при увеличении нагрузки на него эта скорость может падать.

Приведу примеры таких тестов, где us - микросекунды:

https://www.albedotelecom.com/src/lib/WP-RFC2544.pdf
https://www.albedotelecom.com/src/lib/WP-RFC2544.pdf
https://miercom.com/pdf/reports/20121129.pdf
https://miercom.com/pdf/reports/20121129.pdf

А вот пример теста где уже подсчитано количество пакетов (Frames) в секунду:

Согласно публичным тестам задержка (Latency) для пакетов 64 байта в среднем находится от 7 до 50 микросекунд, т.е. от 135 000 до 20 000 уже полученных устройством пакетов в секунду можно с него считать в рамках одного потока.

Это позволяет сделать вывод, что задержка достаточно серьёзная (если само устройство при скорости 10 Мбит/c способно принять 1 250 000 аналогичных пакетов).

Выводы

Одним из самых первых и важных решений при разработке сервера - это обеспечение его работы в многопоточном режиме, т.е. что бы ваш сервис представлял собой несколько потоков (они могут делить между собой память как в языке C#, использовать каналы Channel как я языке Go или PHP или даже быть разными процессами обмен данных между которых можно наладить например установив TCP соединение между, использовать сокет (фаил) для обмена информации и т.д.).

Этот подход можно заметить и в WEB серверах (apache, nginx) и менеджерах процессов PHP - FPM где нужно вручную указать количество этих параллельных потоков (процессов) - они называются в них workers. Имейте в виду, что помимо обработки пакетов сервером для работы ОС нужно оставлять свободными (в зависимости от того что на ней еще работает, например база данных).

Помимо того что бы сервер работал в многопоточном режиме на физической "машине" (железе) должно быть достаточное количество нитей (thread) у ядер CPU что бы ваши потоки (процессы) находились на разных нитях и действительно работали параллельно (в ОС есть встроенный балансировщик на который как я полагаю можно положиться как минимум на первых порах разработки).

Видео-версия этого разбора:

Что было дальше

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

Как это выглядит в игре, показываю на канале проекта: youtube.com/@mmogick_ru.

Если вы упирались в сеть — расскажите, что оказалось потолком: карта, процессор или сам протокол.

История:

  1. Введение

  2. Масштабируемость и асинхронность

  3. WebSocket

  4. Redis

  5. LUA и JavaScript

  6. Выбор технологий, протокола и архитектурный шаблон Entity Component System

  7. Игровые локации (тайловые карты)

  8. Клиентская часть на Unity

  9. Игровые серверные механики

  10. Открытый бесшовный мир в 2D игре

  11. FPS, Ping, паузы между командами, интерполяция и экстраполяция

  12. Очереди и параллельное программирование на CPU

  13. Event-driven паттерн, JSON-RPC и почему не сервисная (SOA) архитектура

  14. Сетевая карта и задержка кадра (Latency frame) по RFC 2544 (1242)

  15. Создание сервера для онлайн ММО игр на PHP

  16. Готовое MVP сервиса 2D MMO RPG игр (realtime)

  17. Внедряю ИИ: механики из одного описания

  18. Конвейер контента для MMO: импорт анимации, экипировка и обновление игры без патчей

  19. Каталог ассетов и git: как командой вести игру в облаке

  20. Тесты для кода, который пишет ИИ: контракты вместо ассертов

  21. ИИ-картограф: как карты сшиваются в бесшовный мир