Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Когда необходимо отправлять небольшие пакеты данных по протоколу TCP, разработка приложения Winsock особенно важна. Дизайн, не учитывающий взаимодействие отложенного подтверждения, алгоритма Нейгла и буферизации Winsock, может резко повлиять на производительность. В этой статье рассматриваются эти вопросы с помощью нескольких исследований. Он также получает ряд рекомендаций по отправке небольших пакетов данных эффективно из приложения Winsock.
Исходная версия продукта: Winsock
Исходный номер базы знаний: 214397
Общие сведения
Когда стек TCP Microsoft получает пакет данных, срабатывает таймер задержки на 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 параметра (это 8K по умолчанию). При необходимости Winsock может буферизать больше, чем размер буфера SO_SNDBUF . В большинстве случаев завершение отправки в приложении указывает, что буфер данных в вызове отправки приложения копируется в буфер ядра Winsock и не указывает, что данные попали в сетевую среду. Единственным исключением является отключение буферизации Winsock, задав SO_SNDBUF значение 0.
Winsock использует следующие правила, чтобы указать завершение отправки в приложение (в зависимости от способа вызова отправки уведомление о завершении может быть функцией, возвращающейся из блокирующего вызова, сигналив о событии или вызове функции уведомлений и т. д.):
Если сокет по-прежнему находится в пределах квоты SO_SNDBUF, Winsock копирует данные из переданного приложением и сообщает приложению о завершении отправки.
Если сокет выходит за рамки
SO_SNDBUFквоты, и в буфере ядра стека сохраняется только одна ранее буферизованная отправка, Winsock копирует данные из приложения и сигнализирует приложению о завершении отправки.Если сокет выходит за рамки
SO_SNDBUFквоты и в буфере ядра стека находится более одной ранее буферизованной отправки, Winsock копирует данные из отправляемых данных приложения. Winsock не указывает приложению индикацию завершения отправки до тех пор, пока стек не завершит достаточное количество отправок, чтобы вернуть сокет в пределыSO_SNDBUFквоты или оставшееся только одно условие на невыполненную отправку.
Пример 1
Клиент Winsock TCP должен отправлять 10000 записей на СЕРВЕР Winsock TCP для хранения в базе данных. Размер записей варьируется от 20 байт до 100 байтов. Чтобы упростить логику приложения, проект выглядит следующим образом:
- Клиент блокирует только отправку. Сервер только блокирует
recv. - Сокет клиента задает
SO_SNDBUFдо 0, чтобы каждая запись выходила в одном сегменте данных. - Сервер вызывает
recvв цикле. Буфер размером 200 байт вrecvпозволяет получать каждую запись с одним вызовомrecv.
Производительность
Во время тестирования разработчик находит, что клиент может отправлять только пять записей в секунду на сервер. Общее число записей 10000, максимальное количество данных в 976 кб (10000 * 100 / 1024), занимает более получаса для отправки на сервер.
Анализ
Поскольку клиент не устанавливает параметр TCP_NODELAY, алгоритм Нейгла заставляет стек TCP ожидать ACK, прежде чем можно будет отправить другой пакет в сеть. Однако клиент отключил буферизацию Winsock, задав SO_SNDBUF параметр 0. Таким образом, 10000 отправленных звонков должны быть отправлены и ACK'ed по отдельности. Каждый ACK задерживается 200 мс, так как в стеке TCP сервера происходит следующее:
- Когда сервер получает пакет, таймер задержки 200 мс отключается.
- Серверу не нужно отправлять что-либо обратно, поэтому ACK не может быть добавлен.
- Клиент не будет отправлять другой пакет, если не будет подтвержден предыдущий пакет.
- Срок действия таймера задержки на сервере истекает, и ACK отправляется обратно.
Как улучшить
Существует две проблемы с этим дизайном. Сначала, есть проблема с таймером задержки. Клиент должен иметь возможность отправлять два пакета на сервер в течение 200 мс. Так как клиент по умолчанию использует алгоритм Нэйгла, ему следует просто использовать стандартную буферизацию Winsock и не устанавливать SO_SNDBUF в 0. Как только стек TCP объединяет буфер, который становится больше, чем максимальная единица передачи (MTU), пакет полного размера отправляется немедленно, не ожидая подтверждения (ACK) от дистанционного узла.
Во-вторых, этот дизайн вызывает одну отправку для каждой записи такого небольшого размера. Отправка такого небольшого размера неэффективна. В этом случае разработчику может потребоваться дополнить каждую запись до 100 байт и отправлять по 80 записей за раз в рамках одного клиентского вызова отправки. Чтобы сообщить серверу, сколько записей будет отправлено в общей сложности, клиент может начать общение с заголовком фиксированного размера, содержащим количество записей, которые должны быть отправлены.
Пример 2
Клиентское приложение Winsock TCP открывает два подключения с приложением TCP-сервера Winsock, предоставляющим службу котировок акций. Первое подключение используется в качестве канала команд для отправки тикера акций на сервер. Второе подключение используется в качестве канала данных для получения котировок акций. После установки двух подключений клиент отправляет на сервер символ акций через канал команд и ожидает возвращения котировок через канал данных. Он отправляет следующий запрос тикера акции на сервер только после получения первой котировки акции. Клиент и сервер не задают параметры SO_SNDBUF и TCP_NODELAY.
Производительность
Во время тестирования разработчик выясняет, что клиент может получить только пять котировок в секунду.
Анализ
Эта конструкция позволяет иметь один активный запрос на котировку акций одновременно. Первый биржевой символ отправляется на сервер через командный канал (подключение), и ответ моментально передаётся с сервера клиенту через канал данных (подключение). Затем клиент немедленно отправляет второй запрос символа акции, и вызов отправки возвращается сразу же, так как буфер запроса в вызове отправки копируется в буфер ядра Winsock. Однако стек TCP клиента не может немедленно отправить запрос из буфера ядра, так как первая отправка по каналу команд еще не подтверждена. После того как истечет таймер задержки на 200 мс в канале команд на сервере, ACK для первого запроса символа возвращается клиенту. Затем второй запрос цены успешно отправляется на сервер после задержки на 200 мс. Цитата для второго символа акций возвращается сразу через канал данных, так как в настоящее время таймер задержки в канале данных клиента истек. ACK для предыдущего ответа на цитату был получен сервером. (Помните, что клиент не мог отправить второй запрос котировок за 200 мс, что дает время для истечения времени задержки на клиенте и отправки ACK на сервер.) В результате клиент получает второй ответ с котировкой и может отправить следующий запрос котировки, который подлежит тому же процессу.
Как улучшить
Здесь не требуется схема с двумя подключениями (каналами). Если вы используете только одно подключение для запроса котировки акций и ответа на него, подтверждение (ACK) для запроса может быть включено в ответ на котировку и возвратиться немедленно. Чтобы повысить производительность, клиент может мультиплексировать несколько запросов котировок акций в один вызов отправки на сервер, а сервер также может мультиплексировать несколько ответов на котировки в один вызов отправки клиенту. Если по какой-то причине требуется использование двух однонаправленных каналов, обе стороны должны установить параметр
TCP_NODELAY, чтобы небольшие пакеты могли отправляться немедленно, не ожидая ACK для предыдущего пакета.
Рекомендации
Хотя эти два примера были сфабрикованы, они помогают проиллюстрировать некоторые худшие сценарии. При разработке приложения, которое включает отправку многочисленных небольших сегментов данных и recvs, следует учитывать следующие рекомендации.
Если сегменты данных не являются временно важными, приложение должно объединить их в единый блок данных, чтобы передать его на отправку. Так как буфер отправки, скорее всего, копируется в буфер ядра Winsock, буфер не должен быть слишком большим. Чуть меньше 8K эффективно. Как только ядро Winsock получает блок больше MTU, оно отправляет несколько полноразмерных пакетов и последний пакет с оставшимися данными. Сторона отправки, за исключением последнего пакета, не будет подвержена воздействию таймера задержки в 200 мс. Последний пакет, если он является пакетом с нечётным номером, по-прежнему подвергается алгоритму отложенного подтверждения. Если стек на стороне отправки получает еще один блок, размер которого больше MTU, он всё ещё способен обойти алгоритм Nagle.
По возможности избегайте подключений сокетов с однонаправленным потоком данных. Коммуникации по однонаправленным сокетам более подвержены влиянию алгоритмов Нейгла и отложенного подтверждения. Если обмен данными осуществляется по схеме запрос-ответ, следует использовать один сокет как для отправки, так и для
recvs, чтобы ACK можно было встроить в ответ.Если все небольшие сегменты данных должны отправляться немедленно, задайте
TCP_NODELAYпараметр в конце отправки.Если вы не хотите гарантировать, что пакет отправляется по сети при завершении отправки, указанном Winsock, не следует устанавливать значение
SO_SNDBUFв ноль. На самом деле, буфер 8K по умолчанию был эвристически определен для эффективной работы в большинстве ситуаций, и вы не должны изменять его, если только вы не проверили, что новый параметр буфера Winsock обеспечивает лучшую производительность, чем по умолчанию. Кроме того, значениеSO_SNDBUFнуля в основном полезно для приложений, которые выполняют массовую передачу данных. Даже тогда для максимальной эффективности его следует использовать в сочетании с двойной буферизацией (несколько ожидающих отправок в любое время) и перекрывающимся вводом-выводом.Если доставка данных не должна быть гарантированной, используйте UDP.
Ссылки
Дополнительные сведения об отложенном подтверждении и алгоритме Nagle см. в следующих материалах:
Брэден, R.[1989], RFC 1122, Требования к узлам Интернета — Коммуникационные уровни, Рабочая группа Инженерного Совета Интернета.