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

3. TCP и UDP — глубже, чем «одно надёжное»


TCP (handshake/завершение/окно/MSS/ретрансмиты/TIME_WAIT/CLOSE_WAIT, Nagle/Delayed ACK), когда и зачем нужен UDP, и что такое QUIC (HTTP/3) — с фокусом на причины задержек и таймаутов.


TCP

Надёжная доставка и поток байтов

TCP (Transmission Control Protocol) — протокол L4 с установлением соединения, подтверждением доставки и повторной передачей при потере. Предоставляет приложению поток байтов в правильном порядке без дубликатов. Порты источника и назначения идентифицируют приложение на каждом конце.

3-way handshake (установление соединения)

Перед передачей данных TCP выполняет «рукопожатие» из трёх шагов:

  1. SYN — клиент отправляет пакет с флагом SYN (и начальный номер последовательности).
  2. SYN-ACK — сервер отвечает SYN + ACK (подтверждение и свой номер последовательности).
  3. ACK — клиент отправляет ACK; после этого соединение считается установленным (ESTABLISHED), можно слать данные.

Каждый шаг добавляет задержку (RTT). При высокой latency установление нового соединения даёт заметную задержку до первого байта данных; отсюда интерес к переиспользованию соединений (HTTP keep-alive, connection pooling).

FIN vs RST

  • FIN — корректное завершение: сторона сообщает «больше данных не будет». Вторая сторона может ещё передавать данные, затем тоже отправить FIN. После обмена FIN соединение переходит в фазу закрытия (см. TIME_WAIT).
  • RST (Reset) — принудительный сброс соединения. Отправляется при ошибке (порт закрыт, неверное состояние, аварийное закрытие приложения). Получатель видит обрыв соединения (например, «connection reset by peer»). RST не несёт данных и не ожидает подтверждения.

Понимание FIN vs RST помогает при отладке: неожиданные RST часто указывают на закрытие порта, firewall или падение процесса на другой стороне.

Window size (окно)

Окно — объём данных (в байтах), который отправитель может передать без ожидания подтверждения. Принимающая сторона объявляет в заголовке TCP своё receive window. Ограничение окна защищает приёмник от переполнения и ограничивает скорость отправителя (flow control). При перегрузке сети или медленном приёмнике окно уменьшается — throughput падает; при «зависших» соединениях иногда видно нулевое окно (zero window).

MSS (Maximum Segment Size)

MSS — максимальный размер полезной нагрузки одного TCP-сегмента (без заголовков). Стороны согласуют MSS при handshake (в опциях SYN). Обычно MSS выбирают так, чтобы сегмент вместе с IP и Ethernet укладывался в MTU канала (например, MTU 1500 → MSS чаще всего 1460 для IPv4). Фрагментация на L3 нежелательна; неверный MSS может приводить к фрагментации или чёрным дырам при строгом MTU на пути.

Retransmission (ретрансмиссия)

При потере сегмента приёмник не отправляет ACK за потерянный диапазон; отправитель по таймауту или по дублирующим ACK (fast retransmit) повторяет передачу. Retransmits в статистике (ss, netstat, tcpdump) или в мониторинге указывают на потери в сети или перегрузку. Высокий процент ретрансмиссий увеличивает задержку и снижает эффективную пропускную способность.

TIME_WAIT и CLOSE_WAIT

  • TIME_WAIT — состояние стороны, которая первой отправила FIN и получила ACK. Соединение держится в этом состоянии обычно 2*MSL (часто 60 секунд), чтобы «долетевшие» с задержкой пакеты не попали в новое соединение с теми же адресами и портами. Большое число сокетов в TIME_WAIT может исчерпывать доступные порты на клиенте при высокой частоте переподключений; смягчают переиспользованием соединений и настройкой SO_REUSEADDR/tcp_tw_reuse (осторожно в production).
  • CLOSE_WAIT — состояние стороны, которая получила FIN и ещё не закрыла сокет со своей стороны. Если сокеты «зависают» в CLOSE_WAIT, значит приложение не закрывает соединение после того, как другая сторона закрыла его. Типичная причина — баг в коде (не вызван close после чтения EOF). Рост числа CLOSE_WAIT указывает на утечку дескрипторов.

!!! tip "Практика"

При проблемах с «обрывом» или нехваткой портов смотреть распределение состояний: `ss -o state time-wait` и `ss -o state close-wait`. Много CLOSE_WAIT — искать в приложении незакрытые соединения.

Nagle и Delayed ACK (на уровне концепции)

  • Nagle — алгоритм на стороне отправителя: маленькие сегменты не отправляются сразу, а объединяются, пока не пришло ACK на предыдущий сегмент. Снижает количество мелких пакетов, но может увеличивать задержку при интерактивном трафике. Часто отключают для низколатентных протоколов (TCP_NODELAY).
  • Delayed ACK — приёмник может задерживать отправку ACK (до 40 ms или до прихода второго пакета данных), чтобы отправить один ACK на несколько сегментов. В комбинации с Nagle иногда даёт заметные задержки; понимание нужно для тюнинга чувствительных к latency приложений.

UDP

Когда и почему используется

UDP (User Datagram Protocol) — протокол без установления соединения и без гарантий доставки. Пакет отправляется с портом источника и назначения; нет handshake, нет ретрансмиссий, нет встроенного flow control. Применяется там, где:

  • важна низкая задержка и допустима потеря части пакетов (стриминг, игры, VoIP);
  • трафик широковещательный или multicast;
  • поверх UDP уже реализована своя логика надёжности (например, QUIC) или надёжность не нужна (DNS-запросы, метрики).

Огненные стены и NAT часто обрабатывают UDP иначе, чем TCP (короткие таймауты сессий, необходимость keep-alive для сохранения маппинга).

QUIC (HTTP/3) — обзор

QUIC — протокол поверх UDP, разработанный для уменьшения задержки установления соединения и улучшения работы при потере пакетов. Встроены шифрование (TLS 1.3) и мультиплексирование потоков. HTTP/3 — это HTTP поверх QUIC вместо TCP. С точки зрения сети: трафик идёт как UDP; для диагностики нужно смотреть UDP-порты (обычно 443) и понимать, что внутри — не «сырой» UDP, а QUIC-сессии. Поддержка в балансировщиках и файрволах пока не везде полная.


Практика: команды

Сокеты и состояния

# Слушающие порты и все TCP/UDP сокеты (современная замена netstat)
ss -lntup

# Только слушающие TCP с портами и процессами
ss -ltnp

# Состояния TCP (например, time-wait, close-wait)
ss -o state time-wait
ss -o state close-wait

Классический netstat

# Все сокеты, адреса в числовом виде, программы
netstat -anp

# Только TCP в состоянии LISTEN
netstat -ltnp

Захват TCP-трафика

# Только TCP-пакеты на интерфейсе eth0
sudo tcpdump -i eth0 tcp

# С флагами (SYN, ACK, FIN, RST)
sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack|tcp-fin|tcp-rst) != 0'

# Порт 443
sudo tcpdump -i eth0 'tcp port 443'

!!! tip "Практика"

При разборе таймаутов и «подвисаний» смотреть: есть ли ретрансмиты (tcpdump, метрики), не забито ли окно (zero window), сколько соединений в TIME_WAIT/CLOSE_WAIT и не исчерпан ли пул портов.

Паттерны и антипаттерны

Паттерн Описание
Переиспользование соединений HTTP keep-alive, connection pooling — меньше handshake и меньше TIME_WAIT.
Мониторинг ретрансмитов и состояний Метрики по retransmits, по числу сокетов в TIME_WAIT/CLOSE_WAIT помогают ловить сетевые и прикладные проблемы.
При CLOSE_WAIT искать в коде Закрывать сокет после чтения EOF; использовать таймауты и корректное завершение при shutdown.
Антипаттерн Почему плохо Что делать
Игнорировать массу CLOSE_WAIT Утечка дескрипторов, в итоге приложение не может открыть новые соединения. Исправить закрытие соединений в приложении.
Открывать новое TCP-соединение на каждый запрос Рост latency и числа соединений в TIME_WAIT. Пулы соединений, keep-alive.

Дополнительные материалы