Алгоритм Нейгла (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), так что опция есть в любом стандартном стеке.
Источники
- 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; на простаивающем соединении пакет уходит сразу; отложенные данные отправляются, когда приходит подтверждение или обновление окна
- 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 включены по умолчанию, для приложений «запрос — ответ» это даёт плохую производительность; многие приложения просто отключают Нейгла опцией сокета; модификация алгоритма в стандарт не включена
- Espressif. ESP-IDF Programming Guide v6.1, ESP32: lwIP — разд. Socket Options: на уровне IPPROTO_TCP поддерживается TCP_NODELAY; разд. Minimum Latency: для TCP-сокетов lwIP поддерживает стандартный флаг TCP_NODELAY, отключающий алгоритм Нейгла
Нашли ошибку? ·
Смотрите также
- TCPНадёжный поток байтов поверх IP: рукопожатие, порты, подтверждения, keep-alive
- Keep-alive TCPЗонды молчащего соединения TCP: по умолчанию выключены, интервал не меньше 2 ч; настройка через опции сокета lwIP
- Окно приёма TCPПоле Window, управление потоком, нулевое окно и зонды, масштабирование окна, TCP_WND в lwIP
- Тройное рукопожатие TCPОбмен SYN, SYN-ACK, ACK при открытии соединения TCP: синхронизация начальных номеров, состояния, защита от старых дубликатов
- MQTTПротокол издатель — подписчик через брокер: топики, QoS, retain, Last Will
- ESP32ESP32, S2, S3, C2, C3, C5, C6, H2, P4: ядра, радио, USB, GPIO — сравнение и выбор
- Ethernet для микроконтроллераW5500, ENC28J60, MAC + PHY по RMII, LAN8720, DP83848, трансформатор и разводка