Низкая производительность возникает при копировании данных на TCP-сервер с помощью программы API сокетов Windows

В этой статье приведены обходные пути для проблемы, из-за которой низкая производительность возникает при копировании данных на TCP-сервер с помощью программы API сокетов Windows.

Применимо к: Windows Server 2012 R2, Windows 10 — все выпуски
Исходный номер базы знаний: 823764

Симптомы

При запуске программы, которая использует API сокетов Windows, производительность может снизиться при копировании данных на TCP-сервер.

Если выполнить трассировку сети с помощью сетевого sniffer, например Microsoft Network Monitor, TCP-сервер отправляет сегмент TCP ACK последнему сегменту TCP в потоке данных TCP в таймере отложенного подтверждения (также известном как таймер отложенного ACK). По умолчанию для операционных систем Windows значение этого таймера равно 200 миллисекундам (мс). Типичный поток данных для отправки 64 килобайт (КБ) данных выглядит примерно так:

Клиент-сервер> 1460 байт
Клиент-сервер> 1460 байт
Сервер-клиент> ACK
Клиент-сервер> 1460 байт
Клиент-сервер> 1460 байт
Сервер-клиент> ACK
....
Клиент-сервер> 1460 байт
Клиент-сервер> 1460 байт
Сервер-клиент> ACK-PUSH
Клиент-сервер> 1296 байт
—> задержка ACK 200 мс

Причина

Эта проблема возникает из-за архитектурного поведения API сокетов Windows и afd.sys. Эта проблема возникает, если выполняются все следующие условия:

  • Программа Windows Sockets использует неблокирующие сокеты.

  • Один вызов отправки или вызов WSASend заполняет весь базовый буфер отправки сокетов.

    Например, программа использует функцию Windows Sockets для изменения буфера отправки сокетов setsockopt по умолчанию на 32 КБ во время подпрограмм инициализации сокетов:

    setsockopt( sock, SOL_SOCKET, 32768, (char *) &val, sizeof( int ));
    

    Позже, когда программа отправляет данные, она выполняет вызов отправки или вызов WSASend и отправляет 64 КБ данных во время каждой отправки:

    send(socket, pWrBuffer, 65536, 0);
    

    В этом сценарии каждый раз, когда программа выдает вызов отправки 64 КБ данных, программа возвращает код ошибки SOCKET_ERROR, если базовый буфер сокета размером 32 КБ заполнен. После вызова функции WSAGetLastError программа получает код ошибки WSAEWOULDBLOCK. Большинство программ используют функцию выбора сокетов Windows для проверка состояния сокета. В этом сценарии функция select не сообщает сокету как записываемому до тех пор, пока клиент не получит неоплаченный сегмент TCP ACK. По умолчанию в среде Windows это может занять до 200 мс из-за отложенного алгоритма подтверждения.

  • Удаленный TCP-сервер подтверждает все сегменты TCP, прежде чем клиент отправит последний сегмент TCP с набором битовой передачи.

Обходной путь

Чтобы обойти эту проблему, используйте любой из следующих методов.

Способ 1. Использование блокирующих сокетов

Эта проблема возникает только с неблокирующими сокетами. При использовании блокирующего сокета эта проблема не возникает, так как afd.sys обрабатывает буфер сокетов по-разному. Дополнительные сведения о программировании блокирующих и неблокирующих сокетов см. в документации по пакету SDK для платформы Майкрософт.

Способ 2. Сделайте размер буфера отправки сокета больше размера буфера отправки программы.

Чтобы изменить буфер отправки сокетов, используйте функцию Windows Sockets, чтобы определить текущий размер буфера отправки сокетов getsockopt (SO_SNDBUF), а затем с помощью setsockopt функции задать размер буфера отправки сокетов. По завершении SO_SNDBUF значение должно быть по крайней мере на 1 байт больше размера буфера отправки программы.

Измените вызов отправки или вызов WSASend, указав размер буфера по крайней мере на 1 байт меньше значения SO_SNDBUF. В предыдущем примере в разделе "Причина" этой статьи можно было изменить вызов setsockopt на следующее значение:

setsockopt( sock, SOL_SOCKET, 65537, (char *) &val, sizeof( int ));

или можно изменить вызов отправки на следующее значение:

send(socket, pWrBuffer, 32767, 0);

Можно также использовать любое сочетание этих значений.

Способ 3. Изменение параметров TCP/IP на TCP-сервере

Важно!

В этот раздел, описание метода или задачи включены действия, содержащие указания по изменению параметров реестра. Однако неправильное изменение параметров реестра может привести к возникновению серьезных проблем. Поэтому следует в точности выполнять приведенные инструкции. Для дополнительной защиты создайте резервную копию реестра, прежде чем редактировать его. Так вы сможете восстановить реестр, если возникнет проблема. Дополнительные сведения о создании резервной копии и восстановлении реестра см. в соответствующей статье базы знаний Майкрософт:
322756 Создание резервной копии и восстановление реестра Windows

Измените параметры TCP/IP на TCP-сервере, чтобы немедленно подтвердить входящие сегменты TCP. Это решение лучше всего подходит для среды с большой базой установки клиента, в которой невозможно изменить поведение программы. В сценариях, в которых удаленный TCP-сервер работает на сервере под управлением Windows, необходимо изменить реестр удаленного сервера. Сведения о том, как изменить таймер отложенного подтверждения, см. в документации по операционной системе.

На сервере под управлением Windows 2000 выполните следующие действия.

  1. Запустите Редактор реестра (Regedit.exe).
  2. Найдите и откройте следующий подраздел реестра: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>
  3. В меню Правка щелкните Добавить значение, а затем создайте следующее значение реестра:
    Имя значения: TcpDelAckTicks
    Тип данных: REG_DWORD
    Значения: 0
  4. Закройте редактор реестра.
  5. Перезапустите Windows, чтобы это изменение вступило в силу.

На сервере под управлением Windows XP или Windows Server 2003 выполните следующие действия.

  1. Откройте редактор реестра.
  2. Найдите и откройте следующий подраздел реестра: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>
  3. В меню Правка выберите пункт Создать, а затем Параметр DWORD.
  4. Присвойте новому значению имя TcpAckFrequency и присвойте ему значение 1.
  5. Закройте редактор реестра.
  6. Перезапустите Windows, чтобы это изменение вступило в силу.

Метод 4. Изменение поведения буферизации в afd.sys для неблокирующих сокетов

Важно!

В этот раздел, описание метода или задачи включены действия, содержащие указания по изменению параметров реестра. Однако неправильное изменение параметров реестра может привести к возникновению серьезных проблем. Поэтому следует в точности выполнять приведенные инструкции. Для дополнительной защиты создайте резервную копию реестра, прежде чем редактировать его. Так вы сможете восстановить реестр, если возникнет проблема. Дополнительные сведения о резервном копировании и восстановлении реестра см. в следующем номере статьи базы знаний Майкрософт: 322756 Резервное копирование и восстановление реестра в Windows

Примечание.

Этот раздел реестра доступен только для Windows Server 2003 с пакетом обновления 1 (SP1) и последующими пакетами обновления.

  1. Нажмите кнопку Пуск, введитеregedit.exeи нажмите кнопку ОК.
  2. Найдите и откройте следующий подраздел реестра:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters
  3. В меню Правка выберите пункт Создать, а затем Параметр DWORD.
  4. Присвойте новому значению имя NonBlockingSendSpecialBuffering и присвойте ему значение 1.
  5. Закройте Редактор реестра.
  6. Перезапустите Windows, чтобы это изменение вступило в силу.

Состояние

Данное поведение является подтвержденной ошибкой продуктов Майкрософт, перечисленных в разделе «Применимо к».

Ссылки

328890 Новая запись реестра для управления поведением подтверждения TCP (ACK) в Windows XP и Windows Server 2003