В этой серии статей речь пойдет о том что такое PING и какими приемами можно сгладить задержку пересылки пакетов при его высоком значении в realtime онлайн играх с примерами кода на C# в игровом движке Unity для игр на ПК, мобильных устройствах и браузерных игр WebGL.
Статья включает в себя видео примеры, демонстрации кода и алгоритм подсчета PING без использований командной строки устройств
Что такое PING
Начнем с теории: отправляя команду в ММО игре, на сервер она доходит не моментально - ей требуется время дойти до физического сервера ("железа") по кабелю, который проложен, в том числе по дну океана и часто этот сервер находится очень далеко (поэтому часто делают несколько серверов в странах, близких к игрокам).
Сервер также может отправлять пакеты игроку на устройство, но т.к. у нас websocket сервер, это не происходит сразу после запроса (хотя можно сделать, но будет неправильно) как, например, в HTTP соединениях (то есть наш сервер подойдет не только к браузерным играм, но и для ПК, мобильных устройств и консолей, однако для демонстрации я использую браузерную на моем сайте).
Время, которое затрачено на отправку пакета и его возврат обратно - называется Ping, выражается (обычно) в миллисекундах (в одной секунде 1000 мс.) и чем он ниже - тем лучше. Важно отметить, что в него не включено время программных вычислений для обработки запроса, как например в расчете RPS сайтов и web приложений
Как высчитать PING
В Unity для версий игр ПК и мобильных устройств есть готовые инструменты анализа PING на устройстве, но в браузерных версиях игр (WebGL) они не работают.
Я расскажу о своем приеме расчета PING, который сможет заменить встроенный инструмент в Unity и использоваться в браузерных версиях игр:
Отправляем с пакетом временную метку Unix Timestamp с точностью до миллисекунды по времени клиента. На скриншоте ниже пример демонстрационной игры с моего сайта (код ее открыт и доступен в GIT) я добавляю метку к каждому запросу (по времени на устройстве пользователя)
-
На сервере, когда нужно отправить пакет по данной команде обратно, я отнимаю от этой метки-времени время, которое прошло с момента как сервер получил данную команду (разница в миллисекундах) - строки
$event['time'] - $microtime
.Важно именно вычитывать время, т.к. если создавать новую метку времени на сервере, эта метка будет иметь рассинхрон времени с клиентом в части миллисекунды, которые для нас важны и пункт ниже покажет неверные результаты.
При получении команд в клиентской части я снова по времени клиента отнимаю полученные значения и это и есть чистый сетевой Ping. Для статистики я сохраняю результаты в массив (список), чтобы выводить среднее значение (сумма всех значений, делённое на количество этих значений в массиве).
-
Периодически шлю попутно с пакетами из клиента на сервер дополнительный параметр
ping
, тем самым сервер знает средний пинг каждого игрока (этот параметр не влияет ни на какие вычисления на сервере и служит как информативный в связи с тем что его легко подменить в клиентской части через код)
Вот и все. С первого взгляда может показаться, что нам нужно знать на какую отправленную команду пришел этот PING, но это не так - мы вычисляем именно время как долго наш пакет шел в сторону сервера (не время работы игровых механик) и обратно по сети, пренебрегая размером пакета и выводя среднее значение.
Однако можно заметить, что PING в нашем случае зависит от размера пакета (вдобавок Websocket протокол на базе TCP в случае потери пакетов начнут их повторно отправлять, будет ждать подтверждение и т.п.), но как по мне это значение более удобно (мне важнее сетевые задержки на реальных игровых пакетах, чем анализ скорости отправки пары байт).
На вопрос каким является хороший показатель Ping в играх Google ответил цифру 50 мс.
Эта цифра означает, что если наш пакет из клиента (игры) будет идти 25 мс. в одну сторону и 25мс. с сервера в клиент (игру), т.е. в другую.
Я подготовил видео, где поясняю и показываю то, о чем написал в данном разделе (функционал уже немного изменился, но концепт остался тот же):
Что такое FPS
Между отправкой любой команды вам нужно время на повторное нажатие клавиши (сенсора), но есть команды, когда можно держать клавишу нажатой (например движение) и можно предположить, что команды шлются на сервер в онлайн играх непрерывно, но это не так - игра поделена на кадры (условно циклы while
) в секунду (FPS) и каждый такой кадр (можно реже, но не чаще обычно) она может считывать нажатие клавиши и меняет картинку. FPS может как взлетать до 300, так и опускаться до 30 (ниже тоже может, но уже видны торможения игры) и даже это слишком быстро, чтобы слать с такой частотой команды (т.к. иначе вы либо будете двигаться при одном нажатии клавиши на доли миллиметра или движение будет настолько быстрым, что будет походить на телепортации)
Возможно Вы знаете из Wikipedia, что глаз человека воспринимает смену изображений как плавную при частоте в диапазоне 48 (фильм "Хоббит" был первым снят с этой частой) - 60 FPS (кадров в секунду), поэтому у каждой команды есть своя пауза и она составляет обычно столько, сколько человеческий глаз может уловить и больше, а значит минимальную комфортную паузу между кадрами можно высчитать по формуле: 1 секунда / 60 FPS = 0,017 (округленно) секунды = 17 мс.
Однако в играх (в том числе realtime стиля "экшен") паузы между командами намного больше, например за одну команду движения персонаж обычно идет больше, чем тот минимум, который мы способны уловить.
В итоге мы приходим к тому, что у каждой команды отправленной игроком должен быть свой таймаут (сервер должен отбрасывать более частые команды одной группы, а клиент не должен слать пакеты чаще, чтобы не забивать канал, в противном случае блокировать пользователя с подозрительной активностью).
Отсюда и считается, что хорошим показателем FPS в играх является 60 FPS
FixedUpdate Unity
В Unity есть циклы, выполняющийся раз в фиксированное времяFixedUpdate
частота которого и выражается в виде миллисекунд. В текущем примере стоят частота 50 FPS
Интерполяция
Когда я начинал разработку игры, все проверки проводились на сервере и отсылал команды без ограничений из клиента, но обрабатывал лишь те, которое пришли спустя прошествии таймаута (например для движения у меня был 300 мс. который мог быть меньше, если у игрока хороший показатель характеристики - скорость). Вообще способы по оптимизации скорости доставки пакетов на сервер более актуальны и заметны на непрерывно выполняющихся анимациях в ответ на команды (движении например) и оптимизация по сути сглаживает время PING между отправкой и получением.
Сервер знает о том какой PING у игрока и первой идеей было снизить на сервере время таймаута на время PING, но я также помнил, что этот параметр приходит от клиента и его можно подделать, тем самым игрок может получить преимущество перед другими (например он будет быстрее двигаться, быстро получать ответы от сервера, но серверу передавать информацию будто у него плохой пинг).
После загромождения сервера кучей формул проверки от идеи что сервер как то использует данные PING игрока я отказался, но саму идею находить среднее время для обработки команд игрока (как в нашем случае) интерполяция, но интерполировать это время мы должны не на сервере, а в клиенте обладая следующими данными:
клиентская часть (игра) при запуске игры должна получать список всех возможных команд с их паузами (и в процессе получать новые значения если эти паузы меняются, например при прокачке игрока)
мы знаем среднее значение него PING на текущий момент, а значит мы знаем когда примерно когда пакет достигнет сервера от момента отправки - 1/2 от скорости пинг
при получении ответа от сервера мы знаем что она выполнилась 1/2 пинга назад (т.к. этот пакет шел до нас это время).
Оперируя этими данными мы знаем примерное (т.к. PING величина не постоянная) время когда сервер будет готов принять новую команду и позволить ее заранее отправить с клиента (не дожидаясь предыдущего ответа от сервера).
В случаях когда команда от клиентской части на сервер придет раньше - на сервере существует очередь в 1 команду на каждую группу (такое бывает когда предыдущая аналогичная команда уже выполняется, но еще не выполнилась или осталось пару миллисекунд до завершения паузы), но постоянный взаимообмен данными между клиентом и сервером (о текущем пинг, о времени выполнения последней команды) делает эту погрешность на непрерывно выполняющиеся команды незначительной.
Видео ниже демонстрирует как работает интерполяция:
Экстраполяция
Выше я уже сказал что PING величина не постоянная и может резко меняться (особенно при работе с беспроводным интернетом) и в моменте он может резко увеличится (ухудшиться) и вернутся на нормальное значение. За это время весь игровой мир может замедлиться - такое явление называют "фризы" (от англ. freez - замерзнуть). Для подобных ситуаций используют подход, который продолжает поведение объекта как если бы он продолжил бы делать то же самое, но в нашем случае (в игре) - с меньшей скоростью, называется экстраполяция
В исполнении демонстрационной онлайн игры она работает следующим образом: в том месте где мы проигрываем анимацию команды которая может выполняться непрерывно (например движение в котором мы можем нажать и держать кнопку) мы сверяем каждый кадр (цикл) текущий пинг и анимация близка к завершению, а новый пакет еще не пришел с сервера на данную команду и пинг вырос то мы продолжаем действие и анимацию как было (в некоторых случаях с замедлением, как с движением), ожидая что новый пакет вот-вот придет и мы продолжим непрерывно анимацию. Более подробно я заложил это формулу в плагине к игре на Unity
А так же подготовил видео с демонстрацией как это работает в самой игре (это улучшило переход в бесшовном мире)
В настоящий момент проект на моем сайте http://my-fantasy.ru является не коммерческим (есть бесплатный демо доступ в админ панель) и я веду его в частном порядке, но верю что продолжая и делясь архитектурными идеями в будущем это станет рабочим продуктом сделанным в России, а статьи помогут в написании игрового сервера для MMO игр.
Буду благодарен за лайк статье.
Если вам интересно чем закончится данный проект - подписывайтесь на мой профиль (я публикую только статьи данного проекта) и следите за новыми видео на Youtube (они выходят чаще чем статьи).
Комментарии (36)
saboteur_kiev
11.06.2023 15:27Не понял, в чем разница между http и "socket сервером", по поводу пинга. По ту сторону веб сервера - такой же сервер, который также само может выполнять какие-то внутренние расчеты, перед тем как вернуть ответ, и также само может выполнять несколько асинхронных запросов параллельно, не дожидаясь ответа на первый.
Если копнуть еще глубже, то сокет сервер может отправить запросы, не дожидаясь ответа, если это UDP, но это не ваш случай.
И игровой пинг, наверное, может включать в себя обработку ответа перед его отправкой, но отдельно созданный для этого icmp как раз показывает именно пинг, то есть скорость доставки пакетов. И уже на базе этой информации можно работать с архитектурой, например если канал данных узкий, а пинг нужен высокий, то размер пакета может быть критичным, и его надо уменьшать вплоть до размера одного фрейма.Если же вы считаете скорость обработки команда, то дело выходит уже не в сети, а возможно в ресурсах на стороне сервера (cpu/gpu/mem/io)
webrobot Автор
11.06.2023 15:27Постараюсь ответить (хотя могу не совсем понять вопроса). Что касательно отличия Http от websocket в плане подсчета пинга это как минимум скорость их работы:
При http запросе у вас запускается Apache/Nginx и каждый раз совершает рукопожатие (а это время) с websocket вы 1 раз соединились и держите постоянное соединение
Если речь про PHP (а в моем примере именно он) то Apache/Nginx подымит (или возьмет из существующих) worker процесс php-fpm (или запустить модуль php если php стоит как модуль Apache например) - а это тоже время на передачу управления процессу. Websocket сервер в данном проекте написан на PHP (т.е. он в нашем случае выступает нашим слушателем порта как Apache/Nginx) ну и вдобавок не отдает ничего (ну кроме системных заголовков что TCP пакет получен, а их меньше чем у HTTP - это тоже экономит время )
Если при работе с http как результат того что пакет дошел мы можем просто прочитать заголовок ответа то с websocket мы ведь не обязаны отдавать ответ (ни пустое с кодом ответа как в HTTP 200 - OK) и получение сообщений сервером от клиента обусловлено лишь обменом системных заголовков TCP что пакет доставлен, однако я не встречал ни в C# ни в JavaScript при работе с websocket методов обработчиков на клиентской стороне успешно доставленных сообщений что бы хотя бы оценить за какое время пакет был доставлен ,а PING это двухсторонний обмен (запрос - ответ), а раз websocket может не отдавать никакого ответа клиенту и анализировать ping у подобного websocket соединения из коробки затруднительно
Suvitruf
11.06.2023 15:27Есть keepalive соединения.
У вас php-сервис прям наружу торчит?
Так вы и по сырому сокету можете ACK-сообщение отправить ????
webrobot Автор
11.06.2023 15:27-1Websocket сервер слушает соединения из вне и если временный ключ пользователя (который дан ему после ввода логина и пароля по http) валиден - установит соединение.
Есть прототип разрабатываемой игры где работает все о чем пишу в статьях
http://my-fantasy.ru/articles/frontend/index/eyJpZCI6Mn0=
webrobot Автор
11.06.2023 15:271.Keepalive держится примерно 10 секунд в стандарных настройках сервера
webrobot Автор
11.06.2023 15:27-1И зачем эти танцы с бубнами ?) Я предлагаю свою реализацию проверки ping основанную не на технических больше решениях , а на логическом приеме
webrobot Автор
11.06.2023 15:27-1если постоянное TCP (на базе него работает и WebSocket) соединение установлено (которое после рукопожатия создается) то сервер может слать запросы не дожидаясь никаких данных от клиента - UDP в этом случае не нужно, но UDP может слать запросы без рукопожатия и установки соединения постоянного если знает куда (там и заголовков меньше но гарантированности доставки пакетов и очередности нет - я отказался от нее тк экономия трафика пока не стоит в приоритетах, а выигрыш в скорости как капля в море на фоне времени затраченного на расчета игровых механик и физики, хотя они составляют сотые доли миллисекунды)
nightblaze
11.06.2023 15:27Если ещё не читали, то рекомендую ознакомиться с https://gafferongames.com/post/udp_vs_tcp/, как и со всей серией Networking for Game Programmers.
webrobot Автор
11.06.2023 15:27-2Я заметил что в статьях где пишут : используй udp взамен tcp для игр тк он быстрый - нигде не приводят сравнение скорости.
Это как знаете сказать - покупайте мазеррати , а не ладу калину т.к. она быстрее
Я не спорю с этими высказываниями. В моих экспераментах выхлоп от использования udp был настолько ничтожен (около 0.01мс выйгрыш скорости который меркнул из за потери пакетов, а проблемм целая куча)
Возможно я буду использовать его когда начну делать 3D игры.
Да, статьи в англоязычном интернете есть и проекты на ютубе есть и сервисы с api. Но в рф почему-то в публичном поле никто своих сервисов с api не создает, а проекты собственных серверов онлайн игр на ютубе (которые сосредотачиваются на статьях вроде udp при этом имея просадки в сотни мс. из за неграмотной архитектуры) часто оказываются провальными (есть видео I hate this game в моей первой статье https://habr.com/ru/articles/669996/ )
nightblaze
11.06.2023 15:27+1Что ж, если вы так зациклились на скорости, то тем более советую почитать статьи о разнице между UDP и TCP. У UDP преимущество не в скорости, а в том, что можно сделать нормальную отзывчивую онлайн игру.
Поиграйте в Ragnarok Online. Если не ошибаюсь, то игра использует как раз TCP и сетевой слой там не очень.
Возможно я буду использовать его когда начну делать 3D игры
2D/3D вообще не имеет отношение к сетевой части.
webrobot Автор
11.06.2023 15:27Мне известна разница между ними которую я изложил в другой статье. Скорость это главный показатель (и в вашей статье именно о ней) . Вы не можете быть уверены что чьято игра медленная только потому , что она использует tcp, а не udp
webrobot Автор
11.06.2023 15:27А с tcp нельзя сделать нормальную "отзывчатую" игру по вашему? :)
saboteur_kiev
11.06.2023 15:27в tcp для каждого игрока нужно устанавливать сессию, в udp это не обязательно.
Для каждой сессии видимо нужно выделить поток...
В общем архитектуру можно по-разному сделать, но для ММО видимо UDP логичный выборwebrobot Автор
11.06.2023 15:27-2если вы уверены что я не прав - вы можете как я в цифрах написать какой выигрыш будет при использовании UDP ?
saboteur_kiev
11.06.2023 15:27Например у вас играет 50 тысяч человек одновременно.
Каждый должен отправлять отчет о своих действиях и получать реакцию сервера.
В случае tcp мы вынуждены что делать? На каждое соединение открывать сессию tcp. Как мы будем ее поддерживать, ведь отправка пакета должна быть подтверждена, то есть у нас будет некий таймаут, если пользователь залагает. Как избежать лага самого сервера в этом случае? открывать эту сессию в отдельном параллельном потоке. Для каждого.
В случае udp это не требуется, отправили и забыли. Но в пакет должна входить нумерация, и если на стороне клиента обнаружена пропажа, активируется синхронизация статуса. Либо некоторые пакеты от клиента могут игнорироваться.webrobot Автор
11.06.2023 15:27Если у меня будет 50.000 у меня будут другие ппоблеммы и другие деньги
Плюс проект масштабируемый и каждая локация это отдельный сервер в тч физический может быть.
Боюсь вы плаваете в понятии потоков , процессов и сессий tcp тк то что вы пишите - чепуха
Пакеты tcp могут отправляться асинхронно и сервер не блокируется в случае если пользователь "залагает"
saboteur_kiev
11.06.2023 15:27Как бы термин ММО и предполагает, что игроков будет не условных 5-10, как в контерстрайке. Это Massive Multiplayer Online проект.
И 50000 я взял не с потолка, а на всяк случай взял что-то сопоставимое с 65535, чисто чтобы указать, что количество исходящих портов может закончиться.
webrobot Автор
11.06.2023 15:27это 11-ая по счету статья. В другой статье про открытый мир я указал , что можно разбить всю игру на локации. Каждая локация может быть на отдельной физической машине - это уже реализовано. Таким образом игроков может быть и 50.000 и 500.000 - лишь бы железа хватило
По моим исследованием VPN с 2мя ядрами CPU и 4GB RAM за который я плачу 8$ в месяц могут уместиться +-4000 игроков и npc суммарно (т.к. последние тоже отправляют команды которые шлют пакеты игрокам об изменении мира)
saboteur_kiev
11.06.2023 15:275000 игроков тянул сервер с двумя ядрами еще 15 лет назад, когда была популярна L2
И мир был поделен на локации, чтобы немного уменьшить количество трафика, дополнительно ограничивая видимость игроков. Тем не менее на крупных ивентах, в одной локации могло взаимодействовать достаточно приличное количество игроков, правда у каждого могли быть свои "лаги", которые наблюдались примерно при 200+ игроков в пределах видимости.
webrobot Автор
11.06.2023 15:27и у меня нет и 5000 игроков. и бросать все ради оптимизации наносекунд с udp протоколом я не готов
webrobot Автор
11.06.2023 15:27а по поводу что было 15 лет назад свечку не держал мне было тогда столько же (не верится что тогда были такие онлайн). А вот Fallout Online: Requiem того же года движок держит не более 500 человек т.к. работает в одном потоке
webrobot Автор
11.06.2023 15:27а вот что я делю на потоки и выполняю параллельно - вы можете увидеть в следующей статье
saboteur_kiev
11.06.2023 15:27и у меня нет и 5000 игроков. и бросать все ради оптимизации наносекунд с udp протоколом я не готов
Мы говорим не про оптимизации вашего конкретного частного случая, а о технологии в целом.
а по поводу что было 15 лет назад свечку не держал мне было тогда столько же (не верится что тогда были такие онлайн)
То есть? онлайн в 2000 одновременно играющих достигался еще в прошлом веке.
А если взять Lineage2, то на официальном российском сервере было около 15-16 шардов, каждый по 4-5 тысяч онлайна (официальный лимит одновременно игроков онлайн был 5000, хотя сервер держал больше, но для достижения идеальной экономики ограничили, чтобы всем игрокам хватало квестовых мобов и локаций. я уж молчу сколько их было на оригинальном корейском.
webrobot Автор
11.06.2023 15:27-2и вы в курсе что UPD пакеты могут не по порядку приходить и теряться по дороге ? Т.к. у меня создается впечатление что вы сами не понимаете о чем пишите
saboteur_kiev
11.06.2023 15:27некоторые пакеты TCP тоже могут приходить не по порядку. Или теряться по дороге, вызывая ретрайны.
webrobot Автор
11.06.2023 15:27TCP гарантирует, что все придет в правильном порядке. Все отправленные пакеты нумеруются
saboteur_kiev
11.06.2023 15:27Так это TCP гарантирует, поскольку этим занимается драйвер самого TCP, грубо говоря. А вот сам пакет может путешествовать, например, разными маршрутами и приходить на сервер в разном порядке.
webrobot Автор
11.06.2023 15:27согласен. но в udp такую систему надо писать а это усложняет и сам код и порог вхождения в проект и как следствие вызовет кучу ошибок
webrobot Автор
11.06.2023 15:27И udp из коробки не будет повторно пересылать потеряные пакеты и нумеровать. А все танцы с бубнами с учетом возможности масштабировония, отсутствия игроков, таймингов о которых я писал выше, с паузами между командами в реалиях - бессмысленно.
Нужно не стремиться сделать идеальный продукт , а быть реалистом , решать реальные проблемы, а не которые могут быть (или не быть с учетом архитектуры и мощности железа) если там когда то будет 50.000 игроков....да если у меня будет хотя бы половина уже буду в "золоте купаться"
webrobot Автор
11.06.2023 15:27-1Паузы между командами (которые даже в realtime играх есть) движения (например, в которое обычно udp делают) обычно в сотни и тысячи раз больше (пусть даже минимум из статьи - 17мс. , что способен уловить ваш глаз) чем выйгрыш который вы получите от использования udp (0.01 мс. за пакет который еще может не дойти). Вот в видо стриминге там да, действительно потоковая передача и это нужно
Так что не стоит слепо использовать все что советуют, а нужно разбираться где и в каких числовых показателях плюс и какие могут быть после проблемы (например забивка канала пакетами, часть которых будет проигнорирована сервером или как в вашей статье сказано в переводе на русский - необходимостью писать свой протокол над udp)
webrobot Автор
11.06.2023 15:27если под обработкой ответа перед отправкой вы подразумеваете танцы с бубнами над пакетами которые составляют протокол websocket - да ping их будет включать, но это время как показала практика ничтожно что им можно пренебречь
webrobot Автор
11.06.2023 15:27я считаю скорость обработки команд игрока (например скорость расчета поиска кратчайшего пути когда игрок отправляет координату куда хочет идти), но это в пинг не включается (у меня в админ панели есть своя статистика для этого - я ее называю RPS игрового сервера и на каждую механику она считается отдельно http://my-fantasy.ru/articles/frontend/index/eyJpZCI6OX0=)
на сайте есть доступ в админ панель для чтения если интересно
Suvitruf
Торможения видны будут и на 60/120 FPS, если он вечно прыгать будет. Поэтому лучше плавные 30, чем вечные прыжки между 30 и 60.
webrobot Автор
я полагаю что в прыжках при расчетах физики в отдельных кадрах просадки будут выдавать FPS ниже чем 60/120 и которые вы будите улавливать, хотя средний FPS останется хорошим.