Окно приёма TCP (receive window)
Приёмник в каждом сегменте сообщает, сколько байт он готов принять. Отправитель не вправе послать больше, чем это окно, пока не получит подтверждение. На микроконтроллере окно — это кусок ОЗУ, и его размер прямо ограничивает скорость.
Обновлено Редакция GAW
Окно приёма TCP (receive window, RCV.WND) — объявляемое в каждом сегменте число байт, которое приёмник готов принять сверх уже подтверждённых. По RFC 9293 (разд. 3.1) это 16-битное поле Window заголовка TCP; отправитель обязан держать в пути не больше неподтверждённых данных, чем объявлено. Так устроено управление потоком: медленный приёмник не даёт быстрому отправителю переполнить свой буфер.
Как работает окно
RFC 9293 (разд. 3.8.6) исходит из того, что окно связано со свободным местом в буфере соединения. Большое окно поощряет передачу, но если данных придёт больше, чем приёмник способен принять, лишнее будет отброшено и вызовет повторные передачи. Малое окно, напротив, может свести обмен к одному сегменту за оборот. Два правила, которые выполняют все стеки:
- Окно не уменьшают задним числом (SHLD-14): объявив 4 КиБ, нельзя затем сказать «2 КиБ», не приняв данных; отправитель обязан пережить такое поведение партнёра (MUST-34).
- Нулевое окно зондируют (MUST-35, MUST-36): когда приёмник объявил 0, отправитель периодически шлёт зонд, первый — по истечении тайм-аута повторной передачи, затем с экспоненциально растущим интервалом. Приёмник может держать окно закрытым неограниченно долго, и пока он отвечает на зонды, соединение остаётся открытым (MAY-8, MUST-37).
Чтобы приёмник не открывал окно по одному байту, а отправитель не слал крошечные сегменты, стандарт требует алгоритмов против «синдрома глупого окна» (SWS) с обеих сторон; на стороне отправителя их дополняет алгоритм Нейгла.
Масштабирование окна
Поле Window 16-битное, то есть не больше 65 535 байт. По RFC 7323 (разд. 1.1) на путях, где произведение полосы на задержку больше 64 КиБ, окно ограничивает пропускную способность: нельзя держать в пути больше данных, чем окно. Опция Window Scale (Kind 3, длина 3) передаётся только в сегментах SYN во время тройного рукопожатия и задаёт сдвиг до 14 бит, что даёт окно до 1 ГиБ (2^(14+16)). Оба конца обязаны послать опцию, иначе масштаб равен 1; поле окна в самих сегментах SYN не масштабируется.
| Окно | RTT 10 мс | RTT 50 мс | RTT 200 мс |
|---|---|---|---|
| 5760 байт (lwIP по умолчанию) | 4,6 Мбит/с | 0,92 Мбит/с | 0,23 Мбит/с |
| 65 535 байт (максимум без масштаба) | 52 Мбит/с | 10,5 Мбит/с | 2,6 Мбит/с |
Верхняя оценка скорости как окно / RTT (наш расчёт); реальная скорость ниже из-за потерь, окна перегрузки и задержанных подтверждений. Пересчитать байты в секунду в биты можно в калькуляторе битрейта и пропускной способности.
Окно в lwIP на микроконтроллере
В ESP-IDF окно приёма новых сокетов задаёт CONFIG_LWIP_TCP_WND_DEFAULT — по умолчанию 5760 байт при MSS 1440, то есть четыре сегмента (наш расчёт). Документация прямо предупреждает: меньшее окно экономит ОЗУ, но значительно снижает производительность. Пиковая память на одно соединение TCP, по странице lwIP, складывается из окна приёма, буфера отправки (CONFIG_LWIP_TCP_SND_BUF_DEFAULT, тоже 5760 байт) и служебной структуры — поэтому окно в 64 КиБ на ESP32 с несколькими соединениями быстро съест кучу. Для отдельного сокета окно меняют через lwip_setsockopt(s, TCP_WINDOW, ...), масштабирование включают CONFIG_LWIP_WND_SCALE с показателем CONFIG_LWIP_TCP_RCV_SCALE от 0 до 14, а размер почтового ящика приёма CONFIG_LWIP_TCP_RECVMBOX_SIZE рекомендовано держать равным окно / MSS + 2. Отдельная сторона вопроса — скорость, с которой прошивка забирает данные из сокета: пока приложение не вызвало recv(), буфер занят, окно не открывается, и отправитель ждёт. Для контроллеров с внешним стеком (W5500 и подобные) окно определяется памятью самого модуля — см. «Ethernet для микроконтроллеров». Долгие паузы с закрытым окном не рвут соединение, но не заменяют keep-alive: зонды идут только от отправителя, у которого есть данные.
Частые вопросы
Как окно приёма ограничивает скорость?
Отправитель не может иметь в пути больше неподтверждённых байт, чем объявленное окно, а подтверждение приходит через время оборота RTT. Значит, скорость не превысит окно / RTT. При окне lwIP по умолчанию 5760 байт и RTT 50 мс это около 115 кбайт/с, примерно 0,9 Мбит/с (наш расчёт), сколько бы ни давал канал. RFC 7323 так и формулирует: когда произведение полосы на задержку больше окна, именно окно ограничивает пропускную способность.
Что такое нулевое окно и что делает отправитель?
Приёмник, у которого кончился буфер, объявляет окно 0, и отправитель останавливается. По RFC 9293 он обязан периодически зондировать окно — слать минимум один октет или повтор, — чтобы узнать, когда оно откроется; первый зонд после тайм-аута повторной передачи, далее интервал растёт экспоненциально. Приёмник может держать окно закрытым сколько угодно, и пока он отвечает на зонды, соединение обязано оставаться открытым — RFC приводит пример принтера, у которого кончилась бумага.
Зачем нужно масштабирование окна?
Поле Window в заголовке 16-битное, то есть не больше 65 535 байт. Для быстрых каналов с большой задержкой этого мало, и RFC 7323 вводит опцию Window Scale: в сегментах SYN стороны договариваются о сдвиге до 14 бит, после чего значение поля умножается на 2^сдвиг — окно до 1 ГиБ. На микроконтроллере в этом редко есть смысл: в lwIP масштабирование включается CONFIG_LWIP_WND_SCALE, но окно всё равно ограничено ОЗУ.
Источники
- IETF. RFC 9293 (STD 7) Transmission Control Protocol (TCP). W. Eddy, Ed., August 2022 — разд. 3.1: поле Window — 16 бит, число октетов данных начиная с указанного в поле подтверждения, которые отправитель сегмента готов принять; значение сдвигается при масштабировании; обязано трактоваться как беззнаковое (MUST-1), рекомендовано хранить окна в 32-битных полях (REC-1); разд. 3.8.6: окно — диапазон номеров, который приёмник готов принять, предполагается связанным со свободным буфером; большое окно поощряет передачу, лишние данные отбрасываются и вызывают повторные передачи; малое окно может ограничить передачу до задержки в один оборот на каждый сегмент; уменьшать объявленное окно настоятельно не рекомендуется (SHLD-14), отправитель обязан это переживать (MUST-34); при нулевом окне отправитель обязан зондировать его (MUST-35, MUST-36), приёмник может держать окно закрытым неограниченно (MAY-8), соединение при этом остаётся открытым (MUST-37); первый зонд — по истечении тайм-аута повторной передачи (SHLD-29), интервал растёт экспоненциально (SHLD-30); разд. 3.8.6.2: алгоритмы против синдрома глупого окна (SWS) у отправителя и приёмника, алгоритм Нейгла дополняет их
- IETF. RFC 7323 TCP Extensions for High Performance. D. Borman, B. Braden, V. Jacobson, R. Scheffenegger, Ed., September 2014 — разд. 1.1: 16-битное поле ограничивает окно 2^16 = 64 КиБ; на путях, где произведение полосы на задержку больше 64 КиБ, окно приёма ограничивает пропускную способность; разд. 2.1: расширение расширяет окно до 30 бит через неявный множитель; разд. 2.2: опция Window Scale — Kind 3, длина 3 байта, поле shift.cnt; посылается только в сегментах SYN, обе стороны обязаны её послать; максимальный показатель 14, максимальное окно 1 ГиБ (2^(14+16)); поле окна в сегменте с SYN не масштабируется; разд. 2.3: SND.WND = SEG.WND << Snd.Wind.Shift, SEG.WND = RCV.WND >> Rcv.Wind.Shift; при shift.cnt больше 14 обязаны использовать 14
- Espressif. ESP-IDF Programming Guide v6.1, ESP32: Configuration Options Reference, Component config > LWIP > TCP; lwIP — CONFIG_LWIP_TCP_WND_DEFAULT — окно приёма по умолчанию для новых сокетов, 5760 байт, диапазон 2440–1 024 000 при CONFIG_LWIP_WND_SCALE, на сокет меняется через lwip_setsockopt(s, TCP_WINDOW, ...), меньшее окно экономит ОЗУ, но значительно снижает производительность; CONFIG_LWIP_TCP_SND_BUF_DEFAULT — буфер отправки, 5760 байт, не меньше 2 MSS, по умолчанию 4 MSS; CONFIG_LWIP_TCP_MSS — по умолчанию 1440, наилучшая пропускная способность при 1460 (IPv4) и 1440 (IPv6); CONFIG_LWIP_WND_SCALE включает масштабирование, CONFIG_LWIP_TCP_RCV_SCALE — показатель 0–14; CONFIG_LWIP_TCP_RECVMBOX_SIZE — рекомендуется LWIP_TCP_WND_DEFAULT/TCP_MSS + 2; страница lwIP (api-guides/lwip.html), разд. Peak Buffer Usage: пиковая память на соединение TCP = tx_win_size + rx_win_size + lwip_tcp_conn
Нашли ошибку? ·
Смотрите также
- TCPНадёжный поток байтов поверх IP: рукопожатие, порты, подтверждения, keep-alive
- Тройное рукопожатие TCPОбмен SYN, SYN-ACK, ACK при открытии соединения TCP: синхронизация начальных номеров, состояния, защита от старых дубликатов
- Алгоритм НейглаПравило TCP против мелких пакетов: новый сегмент ждёт подтверждения предыдущего; отключается опцией TCP_NODELAY
- Keep-alive TCPЗонды молчащего соединения TCP: по умолчанию выключены, интервал не меньше 2 ч; настройка через опции сокета lwIP
- ESP32ESP32, S2, S3, C2, C3, C5, C6, H2, P4: ядра, радио, USB, GPIO — сравнение и выбор
- Полезная скорость интерфейсовБиты на байт, кадры в секунду и доля данных для UART, SPI, I²C, CAN, Modbus и USB
- Ethernet для микроконтроллераW5500, ENC28J60, MAC + PHY по RMII, LAN8720, DP83848, трансформатор и разводка