Перейти к содержанию

Алгоритм Нейгла (Nagle algorithm)

Прошивка отправляет по одному байту на каждое нажатие кнопки, а на другом конце пакеты приходят с заметным опозданием. Или наоборот: прошивка пишет в сокет короткими кусками подряд, и каждый уходит отдельным пакетом — 40 байт заголовков ради нескольких байт данных. За обе ситуации отвечает одно правило в TCP, предложенное Джоном Нейглом в 1984 году.

Обновлено Редакция GAW

Алгоритм Нейгла (Nagle algorithm) — правило TCP из RFC 896: пока по соединению есть отправленные, но не подтверждённые данные, новые мелкие порции от приложения не отправляются, а накапливаются, чтобы уйти одним сегментом. Джон Нейгл описал его в 1984 году как ответ на «проблему мелких пакетов»: при посимвольной передаче с клавиатуры каждый байт данных уходил в пакете из 41 байта — 40 байт заголовков и 1 байт полезной нагрузки, 4000 % накладных расходов, что на загруженных сетях приводило к потерям и повторам.

Как работает

До Нейгла мелкие пакеты задерживали по таймеру на 200…500 мс (так делал Tymnet), но один таймер не подходит и для быстрого Ethernet, и для канала с задержкой в секунды. Решение из RFC 896 обходится без таймеров: запрет отправки безусловен и укладывается в одну-две строки кода TCP. RFC 9293 (разд. 3.7.4) формулирует правило так:

СостояниеДействие отправителя
Неподтверждённых данных нет (SND.NXT = SND.UNA)новый сегмент отправляется сразу, каким бы мелким он ни был
Есть неподтверждённые данные (SND.NXT > SND.UNA)данные буферизуются независимо от бита PSH, пока не придёт подтверждение или не наберётся полный сегмент Eff.snd.MSS

Отложенные данные уходят, как только приходит подтверждение или партнёр увеличивает окно приёма. Поэтому алгоритм сам подстраивается под задержку пути: на быстром канале подтверждения приходят быстро и склейка почти незаметна, на медленном в один сегмент собирается больше данных. На простаивающем соединении первый пакет никогда не задерживается. Реализовать алгоритм стандарт рекомендует (SHLD-7), но требует, чтобы приложение могло отключить его для отдельного соединения (MUST-17). Отправка при этом по-прежнему ограничена медленным стартом и окном партнёра.

Взаимодействие с задержанными ACK

Приёмник TCP, по RFC 9293 (разд. 3.8.6.3), может откладывать подтверждение, но не дольше 0,5 с (MUST-40) и не реже чем на каждый второй полный сегмент. Если приложение делает две мелкие записи подряд и ждёт ответа, вторая запись по правилу Нейгла ждёт подтверждения первой, а приёмник это подтверждение придерживает — до 0,5 с в худшем случае (наш вывод из двух правил стандарта). Приложение A.3 RFC 9293 описывает именно эту картину: в распространённых ОС оба механизма включены по умолчанию, и приложения типа «запрос — ответ» работают плохо, поэтому многие из них просто отключают Нейгла опцией сокета.

TCP_NODELAY в прошивке

Стандартная опция отключения — TCP_NODELAY на уровне IPPROTO_TCP. Документация ESP-IDF для ESP32 перечисляет её среди поддерживаемых lwIP и в разделе о минимальной задержке называет способом отключить алгоритм Нейгла. Ставить её стоит там, где прошивка отправляет короткие команды и ждёт ответа на каждую — например, клиент MQTT, шлющий мелкие публикации, или интерактивная консоль по сети. Для потоковой выгрузки больших буферов опция не нужна: сегменты и так полные, и правило не срабатывает. Два других параметра того же сокета — keep-alive и размер окна — отвечают за живость соединения и управление потоком, а не за задержку. Общие сведения о стеке на микроконтроллере — в статье «Ethernet для микроконтроллеров», установление соединения — в термине «Тройное рукопожатие».

Частые вопросы

Что именно делает алгоритм Нейгла?

Одно правило из RFC 896: если по соединению есть отправленные, но ещё не подтверждённые данные, новые данные от приложения не отправляются, а копятся. В формулировке RFC 9293 (разд. 3.7.4): при SND.NXT > SND.UNA отправитель буферизует всё, независимо от бита PSH, пока не придёт подтверждение или не наберётся полный сегмент размером Eff.snd.MSS. На простаивающем соединении первый сегмент уходит сразу — задержка возникает только у второго и следующих мелких записей подряд.

Когда нужно ставить TCP_NODELAY?

Когда прошивка пишет в сокет мелкими порциями подряд и ждёт ответа на каждую — команды, протокол «запрос — ответ», интерактивный ввод. RFC 9293 (прил. A.3) отмечает, что сочетание алгоритма Нейгла с задержанными ACK, включённых по умолчанию, портит именно такие приложения, и многие просто отключают Нейгла опцией сокета. Для потоковой передачи больших буферов, наоборот, правило почти не мешает: сегменты и так полные.

Как отключить алгоритм Нейгла в lwIP на ESP32?

Опцией сокета TCP_NODELAY на уровне IPPROTO_TCP через setsockopt(): документация ESP-IDF перечисляет её среди поддерживаемых lwIP и в разделе о минимальной задержке прямо говорит, что этот стандартный флаг отключает алгоритм Нейгла. Возможность отключения для отдельного соединения — требование RFC 9293 (MUST-17), так что опция есть в любом стандартном стеке.

Источники

  1. IETF. J. Nagle. Congestion Control in IP/TCP Internetworks. RFC 896, 6 January 1984 — разд. The small-packet problem: при передаче отдельных символов с клавиатуры TCP шлёт пакеты по 41 байту — 1 байт данных и 40 байт заголовков, накладные расходы 4000 %; старое решение (Tymnet) — задержка мелких пакетов на 200…500 мс по таймеру, но подобрать таймер для всех сетей нельзя; разд. The solution to the small-packet problem: запретить отправку новых сегментов, когда от пользователя пришли новые данные, если ранее отправленные данные ещё не подтверждены; безусловно, без таймеров; одна-две строки в коде TCP; на простаивающем соединении пакет уходит сразу; отложенные данные отправляются, когда приходит подтверждение или обновление окна
  2. IETF. W. Eddy (ed.). Transmission Control Protocol (TCP). RFC 9293, STD 7, August 2022 — разд. 3.7.4: алгоритм описан в RFC 896, рекомендован RFC 1122, реализован в большинстве стеков с небольшими вариациями; при наличии неподтверждённых данных (SND.NXT > SND.UNA) отправитель буферизует все данные пользователя независимо от бита PSH, пока не придёт подтверждение или не наберётся полный сегмент Eff.snd.MSS; реализовать следует (SHLD-7), но обязан быть способ отключить для отдельного соединения (MUST-17); отправка также ограничена медленным стартом; с задержанными подтверждениями возможны проблемные взаимодействия; разд. 3.8.6.3: задержанный ACK следует реализовать (SHLD-18), задержка обязана быть меньше 0,5 с (MUST-40), ACK следует отправлять не реже чем на каждый второй полный сегмент (SHLD-19); прил. A.3: в распространённых ОС и алгоритм Нейгла, и задержанные ACK включены по умолчанию, для приложений «запрос — ответ» это даёт плохую производительность; многие приложения просто отключают Нейгла опцией сокета; модификация алгоритма в стандарт не включена
  3. Espressif. ESP-IDF Programming Guide v6.1, ESP32: lwIP — разд. Socket Options: на уровне IPPROTO_TCP поддерживается TCP_NODELAY; разд. Minimum Latency: для TCP-сокетов lwIP поддерживает стандартный флаг TCP_NODELAY, отключающий алгоритм Нейгла
Страница помогла?

Нашли ошибку? ·