Подтвердить что ты не робот

Каков обходной путь для подтверждения с задержкой TCP?

Я отправил онлайн-видеограмму (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.

(http://support.microsoft.com/kb/214397)

Считая это, можно предположить, что обходной путь для отложенного подтверждения в стеке Microsoft TCP выглядит следующим образом:

  • Отключите алгоритм Nagle (TCP_NODELAY).
  • Отключите буфер отправки сокета (SO_SNDBUF= 0), чтобы можно было отправить вызов send для отправки пакета.
  • При вызове send, если дальнейшие данные не будут отправлены немедленно, вызовите send снова с однобайтовыми данными, которые будут отброшены получателем.

При таком подходе второй пакет данных будет приниматься приемником примерно в то же время, что и предыдущий пакет данных. В результате, ACK должен немедленно отправляться от получателя к отправителю (эмулируя, что TcpAckFrequency=1 делает в реестре).

Тем не менее, из моего тестирования это улучшенная задержка составляет лишь примерно половину того, что делает редактирование реестра. Что мне не хватает?


В: Почему бы не использовать UDP?

A: Я выбрал TCP, потому что каждый отправляемый пакет должен прибыть (и быть в порядке); нет пакетов, которые не требуют повторной передачи, если они теряются (или становятся неупорядоченными). Только когда пакеты могут быть отброшены/неупорядочены, UDP может быть быстрее TCP!

4b9b3361

Ответ 1

Начиная с Windows Vista перед вызовом listen перед вызовом connect или (на сервере) необходимо установить параметр TCP_NODELAY. Если вы установили TCP_NODELAY после вызова connect, он фактически не отключит алгоритм Нагле, но GetSocketOption укажет, что Nagle отключен! Все это, как представляется, недокументировано и противоречит тому, чему учат многие учебники/статьи по этому вопросу.

Когда Nagle фактически отключен, отложенные подтверждения TCP перестают вызывать задержку.

Ответ 2

Вам нечего делать. Все обходные пути, которые вы предлагаете, - это помочь протоколам, которые не были должным образом разработаны для работы через TCP. Предположительно, ваш протокол был разработан для работы через TCP, правильно?

Ваша проблема почти наверняка одна или обе из них:

  • Вы вызываете функции отправки TCP с небольшими битами данных, даже если нет причин, по которым вы не могли бы позвонить тогда с большими кусками.

  • Вы не реализовали подтверждения приложений на уровне приложений приложений. Реализуйте их так, чтобы ACK могли копировать их.

Ответ 3

При таком подходе второй пакет данных будет принят приемник примерно в то же время, что и предыдущий пакет данных. Как результат, ACK должен немедленно отправляться от приемника к отправителя (эмулируя то, что TcpAckFrequency = 1 делает в реестре).

Я не уверен, что это всегда вызовет отправку второго отдельного пакета. Я знаю, что у вас отключен Nagle и буфер нулевой отправки, но я видел более странные вещи. Может быть полезно использование некоторых свалок-сапог.

Одна идея: вместо того, чтобы ваш "канарейный" пакет был только одним байтом, отправьте полную стоимость данных MSS (как правило, что, 1460 байт в сети 1500 MTU).

Ответ 4

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

это следующая библиотека, которую я использую для надежных переносов UDP:

http://udt.sourceforge.net/

Ответ 5

Чтобы решить эту проблему, необходимо понять нормальное функционирование TCP-соединений. Telnet - хороший пример для анализа.

TCP гарантирует доставку, подтверждая успешную передачу данных. "Ack" может быть отправлен как сообщение сам по себе, но это вводит довольно некоторые накладные расходы - Ack - очень маленькое сообщение, но протоколы более низкого уровня добавляют дополнительные заголовки. По этой причине TCP предпочитает копировать сообщение Ack на другой отправляемый пакет. Рассматривая интерактивную оболочку через Telnet, существует устойчивый поток нажатий клавиш и ответов. И если есть небольшая пауза при наборе текста, на экране нечего эхо. Единственный случай, когда поток останавливается, - если у вас есть выход без соответствующего ввода. Но так как вы можете читать только так быстро, подождите несколько сотен миллисекунд, чтобы увидеть, есть ли нажатие клавиши для включения Ack.

Итак, подводя итог, у нас есть устойчивый поток пакетов в обоих направлениях, и Ack обычно является контрейлером. Если прерывание потока по причинам применения, задержка Ack не будет замечена.

Вернуться к протоколу: у вас, по-видимому, нет протокола запроса/ответа. Это означает, что Ack не может поддерживаться копией (проблема 1). И в то время как принимающая ОС будет отправлять отдельные Acks, это не будет спамом.

Ваше обходное решение через TCP_NODELAY и два пакета на стороне отправки (Windows) предполагает, что принимающая сторона тоже Windows или, по крайней мере, ведет себя как таковая. Это желаемое за действительное, а не инженерное. Другая ОС может решить подождать, пока три пакета не отправят Ack, что полностью нарушит использование TCP_NODELAY для принудительного добавления одного дополнительного пакета. "Подождите 3 пакета" - это всего лишь пример; существует много других правильных алгоритмов для предотвращения спама Ack, который не будет обманут вашим вторым однобайтным фиктивным пакетом.

Какое реальное решение? Отправьте ответ на уровне протокола. Вне зависимости от ОС, он будет копировать TCP Ack в ответ на ваш протокол. В свою очередь, этот ответ также заставит Ack в другом направлении (ответ тоже является TCP-сообщением), но вы не заботитесь о латентности ответа. Ответ есть только для того, чтобы принимающая ОС копировала первый Ack.

Ответ 6

Я предлагаю вам оставить alogithm и буферы Nagle включенными, поскольку его основная цель - собрать мелкие записи в полные/большие пакеты (это значительно повышает производительность), но в то же время используйте FlushFileBuffers() в сокете после того, как вы сделали отправку на некоторое время.

Я предполагаю, что в вашей игре есть своего рода основной цикл, который обрабатывает материал, а затем ждет времени, прежде чем перейти в следующий раунд:

while(run_my_game)
{
    process_game_events_and_send_data_over_network();
    Sleep(20 - time_spent_processing);
};

Теперь я предлагаю вставить FlushFileBuffers() перед вызовом Sleep():

while(run_my_game)
{
    process_game_events_and_send_data_over_network();
    FlushFileBuffers(my_socket);
    Sleep(20 - time_spent_processing);
};

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

Если это не сработает, было бы полезно, если бы вы разместили немного (псевдо) код, который объясняет, как работает ваша программа.

EDIT: Еще две вещи, которые приходили мне в голову, когда я снова думал о твоем вопросе:

a) Задержанные ACK-пакеты не должны НЕ вызывать какого-либо отставания, поскольку они перемещаются в противоположном направлении от данных, которые вы отправляете. В худшем случае они блокируют очередь отправки. Это, однако, будет разрешено TCP после нескольких пакетов, когда это разрешает полоса пропускания связи и памяти. Поэтому, если у вас нет действительно большой ОЗУ (недостаточно для хранения большей очереди сообщений), или вы действительно трассируете больше данных, чем позволяет ваше соединение, тогда отложенные паки ACK являются оптимизацией и фактически повышают производительность.

b) Вы используете выделенный поток для отправки. Интересно, почему. AFAIK является многопоточным безопасным Socket API, поэтому каждый продуктный поток может отправлять данные самостоятельно - если ваше приложение не требует такой очереди, я бы предложил также удалить этот выделенный поток отправки и с ним дополнительные служебные данные синхронизации и задержать его может вызвать.

Я специально упоминаю о задержке здесь. Поскольку операционная система может решить не сразу назначить поток отправки для выполненияg еще раз, когда он будет разблокирован в своей очереди. Типичные задержки повторного планирования в диапазоне 10 мс, но под нагрузкой они могут увеличиться до 50 мс или более. В качестве рабочего стола вы можете попробовать fiddeling с приоритетами планирования. Но это не уменьшит задержку, налагаемую самой операционной системой.

Btw. вы можете легко тестировать TCP и свою сеть, просто имея один поток на клиенте и один на сервере, который просто воспроизводит пинг/понг с некоторыми данными.

Ответ 7

каждый отправляемый пакет должен прибыть (и быть в порядке);

Это требование является причиной задержки.

Либо у вас есть сеть с незначительной потерей пакетов, в которой UDP будет доставлять каждый пакет, либо у вас есть потеря, в которой TCP выполняет повторную передачу, задерживая все (кратным) интервал повторной передачи (который, по крайней мере, время в оба конца). Эта задержка не является последовательной, поскольку она инициируется потерянными пакетами; джиттер обычно имеет худшие последствия, чем предсказуемая задержка в подтверждении, вызванная комбинацией пакетов

Только когда пакеты могут быть отброшены/неупорядочены, UDP может быть быстрее TCP!

Это легкое предположение, но ошибочное.

Существуют и другие способы повышения скорости снижения, помимо ARQ, которые обеспечивают более низкую задержку: методы прямой коррекции ошибок обеспечивают улучшенную задержку для восстановления сбрасывания за счет дополнительной требуемой полосы пропускания.