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

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

Исходная версия продукта: Winsock
Исходный номер базы знаний: 214397

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

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

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

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

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

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

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

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

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

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

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

Пример 1

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

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

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

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

Анализ

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

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

Улучшение

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

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

Пример 2

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

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

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

  • Анализ

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

  • Улучшение

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

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

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

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

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

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

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

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

Ссылки

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

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