В этой части серии статей про разработку сервера для реалтайм игр я расскажу вам о показателе сетевого устройства которое значительно влияет на количество запросов, которое способен обработать ваш игровой сервер.

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

В конце статьи будет приложена видео версия.

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

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

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

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

Уменьшение размера пакета в первую очередь ?!

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

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

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

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

Почему CPU важен при работе с сетью

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

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

И вот тут и кроется самое основное, что должен предусмотреть разработчик - возможность работы сервера в нескольких параллельных процессах (потоках), а как я рассказал в другой статье за это отвечает количество ядер процессора 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

В следующей статье я расскажу как ускорить ваш TCP сервер в 2 раза отключив лишь один стандартный параметр.

Видео версия данной статьи:

Комментарии (20)


  1. DmitriySun
    19.06.2023 17:15

    Как человек, который копал тему (лет 10 назад), сравнивал реализации т.д., могу сказать, что:
    самое важное, в данном случае, сколько блокировок порождает код на низком уровне. Ибо когда вы заставляете ОС оперировать более чем парой тысяч блокировок, то получается следующая фигня: мощность CPU вы масштабировать можете только линейно, а затраты на обслуживание блокировок на уровне ядра с какого-то момента начинают идти по экспоненте. Потому что любая блокировка - это существенные затраты.


    1. webrobot Автор
      19.06.2023 17:15

      Блокировок чего ?


      1. shuchkin
        19.06.2023 17:15
        +1

        скорее всего речь идет о блокировках на операциях ввода/вывода


        1. DmitriySun
          19.06.2023 17:15

          this!


      1. DmitriySun
        19.06.2023 17:15
        +2

        На низком уровне, для того что бы конкурирующие процессы обслуживали сетевой стек, и ваши конкурирующие потоки могли "одновременно" читать/писать на одном сокете, задействуются примитивы синхронизации - обслуживание которых в какой-то момент начинает сжирать CPU. Рост количества примитивов, вполне линейно поглощается, до какого-то момента (последний раз тестил на 4-х ядерном процессоре). Как только на каждое ядро CPU количество примитивов начинает превышать разумное значение, происходит замедление работы системы. Причем в windows, например, это CPU-время засчитывается в idle - т.е. в какой-то момент роста блокировок таскменеджер показывает, что 90% CPU свободно, а по факту у вас свободное время CPU чуть ли не в 0.
        Как-то так... Совсем уж если просто.


        1. webrobot Автор
          19.06.2023 17:15
          +1

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


          1. DmitriySun
            19.06.2023 17:15

            Допустим, да, ситуацию улучшит. Но у вас конкурирующие читатели, вам же необходимо как-то упорядочить поступаемую информацию. Так ведь? А это опять синхронизация чего-либо.

            P.S. Это не спор, просто если вы строите сервер на PHP и о латентности сетевых карт заговорили...


            1. webrobot Автор
              19.06.2023 17:15

              Так это 14-ая по счету статья. В предыдущей обсуждалась как раз очередь. Если в двух словах все воркеры передают данные в игровой сервер на скорости 1 600 000 в секунду , в нем уже очередь реализована


              1. DmitriySun
                19.06.2023 17:15
                -1

                Извините, я забываюсь, что говорю о проекте на PHP - тут все еще хуже будет на порядок. Желаю удачи, реально. Такие сумасшедшие проекты, если не выстреливают, то дико поднимают скилл. Удачи!


                1. webrobot Автор
                  19.06.2023 17:15

                  Спасибо. То о чем пишу можно реализовать на любом языке тк пишу я про архитектуру



  1. MANAB
    19.06.2023 17:15

    В веб серверах количество воркеров или потоков именно для работы с CPU, а не чтобы максимально быстро данные с сетевой карты считать. Обработка http-запросов занимает от миллисекунд до секунд, и оптимизация микросекундныхзадержек тут на самом последнем уровне. А вот сколько запросов параллельно обсужит от пользователей - как раз на первом, но это количество тоже до тысячи обычно, а то и 64 стандартно.


    1. webrobot Автор
      19.06.2023 17:15

      Я про веб сервера не пишу , но и одно другому не мешает: знаю тесты веб серверов способных до 2млн RPS обработать


      1. MANAB
        19.06.2023 17:15

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

        Я именно этот абзац комментировал. Подход в много воркеров там в основном по другой причине.


    1. webrobot Автор
      19.06.2023 17:15

      Http протокол для игр не рассматриваю


  1. ildarin
    19.06.2023 17:15

    Уменьшение размера пакетов - необходимая часть разработки, но ни как не на этапе проектирования.

    Эм... Т.е. мы архитектурим систему, в которой даже модель передачи данных не определена и оставлена "на будущее" сразу в бэклог? Ой вей) Так в ТЗ и напишем: "а интерфейс для передачи данных делайте любой, мы потом перепишем с нуля заново". А потом выясниться, что наиболее оптимальный вариант не согласуется с принятой моделью или типы не подходят, тогда придется перелопачивать вообще всё.

    (взамен использования текстовых понятных человеку команд, например в том же json) Использование сжатие (которое тратит время перед отправкой пакетов и тормозит процесс)

    А сериализация и десериализация (жсона) происходит мгновенно и ничего не тормозит?

    Одним из самых первых и важных решений при разработке сервера - это обеспечение его работы в многопоточном режиме

    На пыхе тысячу лет не писал, но в шарпах параллелить подключения - это мастхэв. Накрутили даже из коробки возможность асинхронной работы на ThreadPool, так что можно даже руками не писать.

    А про аппаратные ограничения на скорость принятия пакетов даже не задумывался, было любопытно.

    Но судя по этой картинке, если я правильно понял и при размере пакета в 64 байта - задержка 50нс, то в секунду будет 1,21 МБ?

    А тут 9,52 МБ?


  1. AleksandrTm
    19.06.2023 17:15

    Мне вот интересно, причём тут всё же php, если никакой конкретики по реализации нет, а лишь теория по верхам, язык вообще не затрагивается

    Интересна техническая часть по реализации, что использовалось, как в главный процесс пишется из других? Что вынесено по разным процессам и почему для чего

    в целом по больше части вода... =\


    1. webrobot Автор
      19.06.2023 17:15

      Эта статья 14-ая по счету. Я рассказываю только об архитектуре. Сам проект имеет прототип на моем сайте (http://my-fantasy.ru/) и написан на PHP


    1. webrobot Автор
      19.06.2023 17:15

      если я привожу в пример в своих статьях библиотеки - они так же для PHP


    1. webrobot Автор
      19.06.2023 17:15

      исходный код первых версий (старых) я jпубликовал в git https://github.com/webrobot1

      Сами игровые механик - их код на разных языках доступен в админ панели на моем сайте.
      Исходный код (актуальный) игры так же есть в git https://bitbucket.org/_catalogs/unity/src