Проблемы проектирования . Отправка небольших сегментов данных через TCP с Winsock

При необходимости отправки небольших пакетов данных через TCP особенно важна конструкция приложения Winsock. Конструкция, которая не учитывает взаимодействие задержки подтверждения, алгоритма Nagle и буферизации Winsock, может резко повлиять на производительность. В этой статье обсуждаются эти проблемы с помощью нескольких исследований. Кроме того, в нем содержится ряд рекомендаций по эффективному отправке небольших пакетов данных из приложения Winsock.

Оригинальная версия продукта:   Winsock
Исходный номер КБ:   214397

Общие сведения

Когда стек Microsoft TCP получает пакет данных, отключается 200-ms timer delay. При отправлении ACK время задержки сброшено и при следующем получении пакета данных начнется еще одна задержка в 200 мс. Чтобы повысить эффективность как в Интернете, так и в интрасетях, стек TCP использует следующие критерии, чтобы решить, когда отправить один ACK в полученные пакеты данных:

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

Чтобы не захламлить сеть небольшими пакетами данных, стек TCP по умолчанию включает алгоритм Nagle, который совмещая небольшой буфер данных из нескольких вызовов и задержек отправки, пока не будет получен ACK для предыдущего пакета данных, отправленного из удаленного хоста. Ниже 2 исключения из алгоритма Nagle:

  • Если стек совмещая буфер данных, размером больше максимального блока передачи (MTU), полный пакет отправляется немедленно, не дожидаясь ACK с удаленного хоста. В сети Ethernet MTU для TCP/IP составляет 1460 bytes.

  • Параметр socket применяется для отключения алгоритма Nagle, чтобы небольшие пакеты данных доставлялись удаленному хосту TCP_NODELAY без задержек.

Чтобы оптимизировать производительность на уровне приложений, Winsock копирует буферы данных из приложения, отправив вызовы в буфер ядра Winsock. Затем стек использует собственную юристику (например, алгоритм Nagle), чтобы определить, когда действительно поместить пакет на провод. Можно изменить количество буфера ядра Winsock, выделенного в розетке, с помощью параметра SO_SNDBUF (это 8K по умолчанию). При необходимости Winsock может буферить больше размера SO_SNDBUF буфера. В большинстве случаев завершение отправки в приложении указывает только на то, что буфер данных в вызове отправки приложения копируется в буфер ядра Winsock и не указывает, что данные попали в сетевой носитель. Единственным исключением является отключение буферизации Winsock с помощью параметра SO_SNDBUF 0.

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

  • Если розетка по-прежнему SO_SNDBUF квота, Winsock копирует данные из отправки приложения и указывает завершение отправки в приложение.

  • Если в буфере ядра стека остается только одна ранее буферная отправка, Winsock копирует данные из отправки приложения и указывает на завершение отправки в SO_SNDBUF приложение.

  • Если в буфере ядра стека больше одной ранее буферной отправки, winsock копирует данные из отправки SO_SNDBUF приложения. Winsock не указывает завершение отправки в приложение до тех пор, пока стек не завершит достаточное количество отправки, чтобы вернуть розетку в рамках квоты или только одно невыполненное условие SO_SNDBUF отправки.

Пример 1

Клиент TCP Winsock должен отправить 10000 записей на сервер TCP Winsock для хранения в базе данных. Размер записей варьируется от 20 до 100 bytes длиной. Чтобы упростить логику приложения, выполните проект следующим образом:

  • Клиент блокирует только отправку. Сервер блокирует recv только.
  • Клиентская розетка задает 0 так, чтобы каждая запись SO_SNDBUF вышла в одном сегменте данных.
  • Сервер recv вызывается в цикле. В буфере recv размещено 200 bytes, так что каждая запись может быть получена в одном recv вызове.

Производительность

Во время тестирования разработчик находит, что клиент может отправлять на сервер только пять записей в секунду. Общее число записей 10000, максимум 976 кб данных (10000 * 100 / 1024), для отправки на сервер занимает более получаса.

Анализ

Так как клиент не за набором параметра, алгоритм Nagle заставляет Стек TCP ждать ACK, прежде чем он сможет отправить другой пакет TCP_NODELAY на проводе. Однако клиент отключил буферику Winsock, установив SO_SNDBUF параметр 0. Поэтому 10000 отправленных вызовов должны отправляться и ACK'ed по отдельности. Каждый ACK задерживается на 200 мс, так как в Стеке TCP сервера происходит следующее:

  • Когда сервер получает пакет, отключается его 200-ms timer delay.
  • Серверу не нужно отправлять что-либо обратно, поэтому ACK не может быть свинарным.
  • Клиент не будет отправлять другой пакет, если предыдущий пакет не будет признан.
  • Срок действия времени задержки на сервере истекает, а ACK отправляется обратно.

Как улучшить

Существует две проблемы с этим дизайном. Во-первых, существует проблема времени задержки. Клиент должен иметь возможность отправлять два пакета на сервер в пределах 200 мс. Поскольку клиент использует алгоритм Nagle по умолчанию, он должен просто использовать буферию Winsock по умолчанию, а не SO_SNDBUF 0. После того, как стек TCP совмещая буфер размером больше максимального блока передачи (MTU), полный пакет отправляется немедленно, не дожидаясь ACK от удаленного хоста.

Во-вторых, эта конструкция вызывает один отправку для каждой записи такого небольшого размера. Отправка этого небольшого размера неэффективна. В этом случае разработчику может потребоваться вкладки каждой записи до 100 bytes и отправка 80 записей одновременно с одного клиентского вызова отправки. Чтобы сервер знал, сколько записей будет отправлено в общей сложности, клиент может начать общение с загона размером с исправление, содержащее количество записей, которые следует выполнять.

Пример 2

Клиентская заявка Winsock TCP открывает два подключения с серверным приложением Winsock TCP, предоставляющим службу котировок акций. Первое подключение используется в качестве командного канала для отправки символа акций на сервер. Второе подключение используется в качестве канала данных для получения котировки акций. После того как эти два подключения установлены, клиент отправляет символ акций на сервер через командный канал и ждет, когда котировки акций будут возвращаться через канал данных. Следующий запрос символа акций отправляется на сервер только после того, как будет получена первая котировка акций. Клиент и сервер не устанавливают параметр SO_SNDBUF и TCP_NODELAY параметр.

  • Производительность

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

  • Анализ

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

  • Как улучшить

    Здесь нет необходимости в проектировании двух подключений (каналов). Если для запроса и ответа на котировки акций используется только одно подключение, ACK для запроса на кавычка может быть свинарным в ответе на кавычка и немедленно возвращается. Чтобы повысить производительность, клиент может использовать несколько запросов на котировки акций в один почтовый вызов на сервер, а сервер также может несколько ответов на несколько цитат в один вызов для клиента. Если два однонаправленных канала по какой-либо причине необходимы, обе стороны должны установить параметр, чтобы небольшие пакеты можно было отправить немедленно, не дожидаясь ACK для предыдущего TCP_NODELAY пакета.

Рекомендации

Хотя эти два примера сфабрикованы, они помогают проиллюстрировать некоторые худшие сценарии. При разработке приложения, в которое входит обширный небольшой сегмент данных, отправляет recvs и, следует учитывать следующие рекомендации:

  • Если сегменты данных не являются критически важными во времени, приложение должно совмесить их в больший блок данных для передачи на вызов отправки. Так как буфер отправки, скорее всего, будет скопирован в буфер ядра Winsock, буфер не должен быть слишком большим. Чуть менее 8K является эффективным. Пока ядро Winsock получает блок больше MTU, он будет отправлять несколько полноразлиберных пакетов и последний пакет с тем, что осталось. Отправляемая сторона, за исключением последнего пакета, не будет поражена 200-ms timer delay. Последний пакет, если это нечетный пакет, по-прежнему подчиняется алгоритму задержки подтверждения. Если конечный стек отправки получает еще один блок больше MTU, он может обойти алгоритм Nagle.

  • По возможности избегайте подключений к розетке с однонаправленным потоком данных. На связь над однонаправленными розетками легче влияют алгоритмы nagle и задержки подтверждения. Если сообщение следует запросу и потоку откликов, следует использовать одну розетку для отправки и для того, чтобы ACK можно было перенаправить в recvs ответ.

  • Если все небольшие сегменты данных должны быть отправлены немедленно, установите TCP_NODELAY параметр в конце отправки.

  • Если вы не хотите гарантировать отправку пакета по проводу, если завершение отправки указывает Winsock, не следует задать значение SO_SNDBUF нулю. По сути, буфер 8K по умолчанию был по умолчанию настроен на то, чтобы работать хорошо в большинстве ситуаций, и изменить его не следует, если только вы не протестировали, что новый параметр буфера Winsock обеспечивает лучшую производительность, чем по умолчанию. Кроме того, установка нуля в основном полезна для приложений, которые SO_SNDBUF массово передают данные. Даже в этом случае для максимальной эффективности следует использовать его совместно с двойной буферикой (несколько непогашенных отправки в любой момент времени) и перекрытием I/O.

  • Если доставка данных не должна быть гарантирована, используйте UDP.

Ссылки

Дополнительные сведения о задержке подтверждения и алгоритме Nagle см. в следующих сведениях:

Braden, R.[1989], RFC 1122, Requirements for Internet Hosts--Communication Layers, Internet Engineering Task Force.