Я отправил онлайн-видеограмму (grid-based), которая использует протокол TCP для обеспечения надежной связи в топологии сети сервера и клиента. Моя игра работает достаточно хорошо, но страдает более высокой, чем ожидалось, латентностью (аналогичные игры в жанре TCP, похоже, лучше справляются с минимальной задержкой).
Во время расследования я обнаружил, что время ожидания только неожиданно велико для клиентов, работающих под управлением Microsoft Windows (в отличие от клиентов Mac OS X). Кроме того, я обнаружил, что если клиент Windows устанавливает TcpAckFrequency=1
в реестр и перезагружает свою машину, их латентность становится нормальной.
Похоже, что мой дизайн сети не учитывал задержанное подтверждение:
Конструкция, которая не учитывает взаимодействие с задержкой подтверждения, алгоритм Нэгла и буферизация Winsock, может резко повлиять на производительность. (http://support.microsoft.com/kb/214397)
Однако мне кажется, что почти невозможно принять во внимание задержку подтверждения в моей игре (или любой игре). Согласно MSDN, стек Microsoft TCP использует следующие критерии, чтобы решить, когда отправлять один ACK на принятые пакеты данных:
- Если второй пакет данных принят до истечения таймера задержки (200 мс), отправляется ACK.
- Если есть данные, которые должны быть отправлены в том же направлении, что и ACK до того, как будет принят второй пакет данных и истечет таймер задержки, ACK связывается с сегментом данных и отправляется немедленно.
- Когда таймер задержки истекает (200 мс), отправляется ACK.
Считая это, можно предположить, что обходной путь для отложенного подтверждения в стеке Microsoft TCP выглядит следующим образом:
- Отключите алгоритм Nagle (TCP_NODELAY).
- Отключите буфер отправки сокета (
SO_SNDBUF
= 0), чтобы можно было отправить вызовsend
для отправки пакета. - При вызове
send
, если дальнейшие данные не будут отправлены немедленно, вызовитеsend
снова с однобайтовыми данными, которые будут отброшены получателем.
При таком подходе второй пакет данных будет приниматься приемником примерно в то же время, что и предыдущий пакет данных. В результате, ACK
должен немедленно отправляться от получателя к отправителю (эмулируя, что TcpAckFrequency=1
делает в реестре).
Тем не менее, из моего тестирования это улучшенная задержка составляет лишь примерно половину того, что делает редактирование реестра. Что мне не хватает?
В: Почему бы не использовать UDP?
A: Я выбрал TCP, потому что каждый отправляемый пакет должен прибыть (и быть в порядке); нет пакетов, которые не требуют повторной передачи, если они теряются (или становятся неупорядоченными). Только когда пакеты могут быть отброшены/неупорядочены, UDP может быть быстрее TCP!