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 выполняет «рукопожатие» из трёх шагов:
- SYN — клиент отправляет пакет с флагом SYN (и начальный номер последовательности).
- SYN-ACK — сервер отвечает SYN + ACK (подтверждение и свой номер последовательности).
- 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. |