Все говорят о синхронизации времени, но дело вовсе не во времени! Речь идет о «времени». Сначала я предполагал, что все говорят о системном времени, но после долгих исследований это была худшая идея. Использование времени — плохая идея, потому что клиенты могут манипулировать системным временем, и поэтому мы, как сервер, не можем на него полагаться. Вместо этого нужно использовать что-то более абстрактное. В игровой сети нам нужно знать, какие данные, события, пакеты относятся к какому фрейму. Обычно эту метку времени включают в пакет. Так о чем это? Речь идет о синхронизации определенной частоты обновления на клиенте, чтобы убедиться, что он отправляет достаточное количество событий в нужное время для буфера на стороне сервера, чтобы он мог правильно прогнозировать движение клиента и состояние мира. Мне пришлось часами искать эту информацию, что вызвало много путаницы в моих мозгах.

Когда клиент запускает игру, он начинает отсчет с состояния сервера. Цель состоит в том, чтобы клиент работал с той же скоростью, что и сервер, чтобы он мог в любое время доставлять входные данные от клиента к серверу. Если вы будете ждать достаточно долго и не будете поддерживать эту скорость симуляции, то клиент начнет отдаляться от сервера. Это может быть вызвано задержкой в ​​thread.sleep/latency/lag. Важно регулярно проверять, не нуждается ли клиент в корректировке.

Алгоритм синхронизации времени

Я еще не реализовал синхронизацию часов, поэтому не буду много говорить об этом. Я нашел два метода, один с какого-то несуществующего сайта, а другой с game dev.net. Наконец, я нашел другие альтернативы в книге по многопользовательской игре. Я бы посоветовал прочитать их, но также немного больше исследовать себя.

Способ 1

https://web.archive.org/web/20181107022429/http://www.mine-control.com/zack/timesync/timesync.html

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

Простой алгоритм с этими свойствами выглядит следующим образом:

  1. Клиент отмечает текущее местное время в пакете «запрос времени» и отправляет на сервер
  2. После получения сервером сервер отмечает время сервера и возвращает
  3. После получения клиентом, клиент вычитает текущее время из отправленного времени и делит на два, чтобы вычислить задержку. Он вычитает текущее время из времени сервера, чтобы определить дельту времени клиент-сервер, и добавляет половинную задержку, чтобы получить правильную дельту часов. (Пока этот алгоритм очень похож на SNTP)
  4. Первый результат следует немедленно использовать для обновления часов, так как он установит локальные часы, по крайней мере, в правильное приблизительное положение (по крайней мере, в правильный часовой пояс!)
  5. Клиент повторяет шаги с 1 по 3 пять или более раз, каждый раз делая паузу в несколько секунд. Другой трафик может быть разрешен на время, но его следует свести к минимуму для достижения наилучших результатов.
  6. Результаты получения пакетов накапливаются и сортируются в порядке от наименьшей до наибольшей задержки. Средняя задержка определяется путем выбора средней точки из этого упорядоченного списка.
  7. Все образцы выше примерно 1 стандартного отклонения от медианы отбрасываются, а оставшиеся образцы усредняются с использованием среднего арифметического.

Единственная тонкость этого алгоритма заключается в том, что пакеты выше медианы на одно стандартное отклонение отбрасываются. Целью этого является устранение пакетов, которые были повторно переданы TCP. Чтобы визуализировать это, представьте, что по протоколу TCP была отправлена ​​выборка из пяти пакетов, и повторной передачи не было. В этом случае гистограмма задержки будет иметь одномодовый (кластер) центр вокруг средней задержки. Теперь представьте, что в другом испытании повторно передается один пакет из пяти. Повторная передача приведет к тому, что этот один образец упадет далеко вправо на гистограмме задержки, в среднем вдвое дальше, чем медиана основного режима. Просто вырезая все выборки, которые отклоняются от медианы более чем на одно стандартное отклонение, эти паразитные моды легко устраняются, если предположить, что они не составляют основную часть статистики.

Метод 2

  1. При присоединении к игре попросите клиента отправить серверу пакет запроса синхронизации времени с текущим местным временем клиента.
  2. Когда сервер получает этот пакет, сервер отправляет обратно клиенту ответный пакет синхронизации времени со следующим: а) местное время, которое клиент первоначально отправил на сервер б) текущее игровое время сервера.
  3. Когда клиент получает ответный пакет, он вычитает местное время в пакете из своего текущего местного времени. Это дает ему значение пинга туда и обратно.
  4. Клиент делит пинг туда и обратно на 2, чтобы получить приблизительное значение задержки в одну сторону.
  5. Затем клиент добавляет одностороннюю задержку, которую он только что вычислил, ко времени пинга сервера, которое он получил в пакете.
  6. Клиент устанавливает свое игровое время на это вновь рассчитанное время.


ФАПЧ

Еще один алгоритм, напрямую связанный с синхронизацией времени (хотя в играх он практически не используется103), — это так называемый цикл фазовой автоподстройки частоты (PLL). Строго говоря, PLL на самом деле не синхронизирует время; что он делает, так это создает часы, которые синхронизируются (как по частоте, так и по фазе) с входящим сигналом (в нашем случае с пакетами, поступающими с сервера, как показано на рис. 3.2). Однако очень часто такие часы, синхронизированные с поступающим сигналом, — это именно то, что нам нужно. Это особенно верно, когда речь идет о наших буферах при получении, так как такие часы будут отвечать на вопрос «когда ожидать следующий пакет» почти наилучшим образом.

Цитата из «Разработка и развертывание многопользовательских онлайн-игр, Vol. Я."

НТП

Другой вариант — использовать протокол, подобный NTP, используя ваш сервер в качестве своего рода источника NTP. Хотя, если идти по этому пути, не используйте настоящий NTP; синхронизация общесистемного времени вашего Клиента с вашим Сервером, а не просто синхронизация внутриигрового времени, не то, что оценят ваши игроки.

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

Цитата из «Разработка и развертывание многопользовательских онлайн-игр, Vol. Я."

часть 0 (введение)
часть 1 (интервал и тики)
часть 2 (время, такт, синхронизация часов)
часть 3 (RTT, PING, задержка, задержка)
часть 4 (экстраполяция на стороне клиента, также известная как точный расчет/интерполяция)
часть 5 (сжатие, дельта-кодирование, управление процентами, упаковка битов) )
часть 6 (событие, ввод, командный звонок/буфер/приоритет/очередь)
часть 7 (детерминированные и состояния)
часть 8 (настоящее, прошлое, будущее, где я)
часть 9 (Бонус, модель Overwatch)

Ресурсы

y4pp.wordpress.com

Форум разработчиков игр:

https://www.gamedev.net/forums/topic/648861-serverclient-time-synchronization/

https://www.gamedev.net/forums/topic/683580-server-client-ticks-lag-compensation-game-state-etc/

https://www.gamedev.net/forums/topic/704579-need-help-understanding-tick-sync-tick-offset/

https://www.gamedev.net/forums/topic/698005-why-keep-client-and-server-ticks-in-sync/

Реализация синхронизации часов

https://pastebin.com/EaAK9Fce

https://forum.unity.com/threads/multiplayer-physics-game-where-collisions-between-players-is-the-core.685540/