UDP (User Datagram Protocol): connectionless, 8-byte header, no retransmit, no ordering. Когда выбирать вместо TCP: DNS, gaming, VoIP, video, metrics. Сравнение latency и переход к QUIC/HTTP/3.
KEKey · @kuzminykh_igor_b3550a9b
0 stars
0 views
94d ago · last update
udp.js·3 scenarios
Loading canvas…
Что это
UDP (User Datagram Protocol) — транспортный протокол, который добавляет к IP только две вещи: порты и checksum. Никаких соединений, sequence numbers, acknowledgements, retry, ordering, flow control, congestion control. Заголовок — 8 байт против 20-60 у TCP. Семантика: «бросил пакет в сеть и забыл». Если пакет потеряется, дублируется или придёт не в том порядке — это проблема приложения, не протокола.
Звучит как недостаток, но это и есть фича. UDP — когда retry либо бесполезен (свежие данные ценнее старых), либо слишком дорог (handshake съест бюджет latency), либо умнее реализован руками (QUIC, RTP). Именно на UDP построены DNS, видеозвонки, онлайн-игры, NTP, DHCP и HTTP/3.
Как работает
UDP-пакет — это (src_port, dst_port, length, checksum) + payload. Всё. Никаких state machines, никаких таблиц соединений на стороне ядра (только сокет, привязанный к порту). Это значит: zero setup cost, zero teardown, zero memory per peer на сервере.
DNS-запрос — канонический пример. Резолверу нужен IP для example.com. Запрос помещается в один UDP-пакет (~40 байт), ответ тоже (~60 байт). Один RTT — и готово. Если бы это был TCP, пришлось бы платить за 3-way handshake (1.5 RTT) до того, как послать сам запрос — итого 2.5-3 RTT вместо одного. На холодном соединении к удалённому DNS это разница между 50мс и 150мс. Помножьте на 20-40 DNS-запросов при загрузке средней страницы — и получите секунду overhead'а на пустом месте.
Что если UDP-ответ потеряется? Резолвер сам ретраит через 1-2 секунды — это app-layer reliability. UDP-протоколу не нужно встраивать retry, потому что DNS-клиент и так знает, когда нужно повторить: если ответа нет за timeout. И знает лучше, чем generic TCP: для DNS повторить весь запрос дешевле, чем держать соединение и ждать ACK.
Если ответ большой (DNSSEC, много A-записей) и не помещается в 512 байт — DNS-сервер ставит бит TC (truncated), клиент переключается на TCP/53. UDP как fast path, TCP как fallback — классический паттерн.
Игровой стрим — потеря лучше задержки. Игровой сервер тикает 60 раз в секунду и шлёт каждому клиенту snapshot мира (позиции, выстрелы, события). Между тиками — 16мс. Если один snapshot потерялся в сети, ретраить его бессмысленно: пока retry дойдёт, уже два следующих snapshot'а будут в полёте, и потерянный устарел. Клиент просто интерполирует между предыдущим и следующим — 16мс jitter, игрок не заметит.
TCP в этом сценарии — катастрофа. Head-of-line blocking: TCP-стрим гарантирует in-order delivery, поэтому если потерялся пакет N, всё что пришло после (N+1, N+2, ...) висит в буфере и ждёт, пока ретранслируется N. Retry на TCP — 200-500мс. Игрок видит «фриз» на полсекунды, потом резкий jump позиций — всё, что накопилось в буфере, проигрывается разом. Это хуже чем потеря: лаг + дезориентация. Поэтому Counter-Strike, Fortnite, Valorant, Overwatch — все используют UDP.
Ввод от клиента (WASD, mouse) тоже UDP fire-and-forget. Сервер — source of truth. Клиент не получил ACK на свой ввод? Не страшно — следующий tick сервер пришлёт актуальное состояние, и клиент скорректируется.
TCP vs UDP в цифрах. Чтобы отправить 100 байт через TCP: SYN (RTT 0), SYN-ACK (RTT 0.5), ACK (RTT 1), PSH+ACK с данными (RTT 1.5), ответ (RTT 2). При RTT=50мс — 100мс на handshake + 50мс на обмен = 150мс. Плюс TCP slow-start ограничивает первое окно (initial cwnd=10), так что большой ответ может занять ещё несколько RTT.
UDP: один пакет туда (50мс), один обратно (50мс) — 50мс total. Втрое быстрее, если один запрос-ответ помещается в MTU (~1400 байт payload) и потеря пакета терпима либо обрабатывается на app-layer.
QUIC (HTTP/3) — UDP с TCP-семантикой. Cloudflare и Google поняли: TCP-абстракция в ядре устарела, multiplexing HTTP/2-стримов поверх одного TCP-соединения страдает от head-of-line blocking. Решение: построить новый транспортный протокол поверх UDP в userspace. QUIC даёт: 0-RTT resumption (нет handshake при reconnect), независимые streams (потеря в одном не блокирует другие), built-in TLS 1.3, connection migration (переключился с Wi-Fi на LTE — соединение не рвётся, потому что connection ID не зависит от IP/port). Всё это нельзя сделать поверх TCP без изменения ядер на каждом устройстве в мире — а поверх UDP можно деплоить пакетным обновлением приложения.
Что на диаграмме
Три независимых сценария, каждый со своими хостами:
Слева — DNS: клиент с браузером и локальным resolver'ом, удалённый DNS-сервер на UDP/53. Edge от resolver к серверу помечен как UDP. Показывает классический request-response в один пакет.
По центру — игра: клиент (геймпад) и сервер с 60Hz tick loop. Один UDP-edge для двустороннего gameplay-трафика. Показывает поток snapshot'ов и обработку потери пакета.
Справа — TCP vs UDP: один клиент с двумя серверами (TCP и UDP). Два edge'а с разными типами. Показывает разницу в latency: 200мс handshake против 50мс fire-and-forget, плюс куда движется индустрия (QUIC).
Цвета хостов — синий (клиент), зелёный (DNS-сервер), фиолетовый (игра), оранжевый (сравнение) — чтобы визуально разделить сценарии без вложенности.
Trade-offs (ADR)
Контекст. Транспортный протокол выбирается один раз и больно меняется. Default — TCP, потому что reliability «бесплатна» (на самом деле платишь handshake'ом и head-of-line blocking). Но есть классы задач, где TCP делает хуже.
Решение: выбирай UDP, если выполняется хотя бы 2 из:
Потеря пакета не хуже задержки. Свежие данные ценнее старых: видео-фрейм, snapshot мира, метрика, NTP-time. Retry на TCP заморозит весь поток на 200мс — а к этому моменту данные уже неактуальны.
Запрос-ответ помещается в один пакет (< MTU 1400). DNS, STUN, syslog. Платить 3 RTT за handshake ради 1 RTT обмена — математика не сходится.
Нужен multicast / broadcast. TCP принципиально point-to-point. DHCP, mDNS, video multicast — только UDP.
Latency бюджет жёсткий (< 100мс end-to-end). VoIP, AR/VR, real-time games. TCP handshake съест половину бюджета.
Нужен ordered byte stream без app-layer reordering — TCP бесплатно даёт то, что иначе придётся писать руками.
Соединение долгое (минуты-часы): NAT-таблицы выкидывают UDP «сессии» через 30 секунд idle — придётся слать keepalive heartbeat'ы.
ISP или firewall в пути блокирует/throttle'ит UDP — некоторые корпоративные сети режут весь не-TCP трафик кроме DNS.
Главная ловушка: «UDP быстрый» — это про latency, не про throughput. На больших объёмах TCP с congestion control обгонит наивный UDP, потому что наивный UDP-залп вызовет потери в первом же bottleneck-роутере, и без backoff'а получится «UDP storm» — ещё больше потерь. Поэтому в production UDP-протоколах (QUIC, RTP, WebRTC DataChannel) congestion control реализован руками поверх UDP — обычно по образцу TCP CUBIC или новых алгоритмов вроде BBR.
Реальные системы
DNS — UDP/53 первичный, TCP/53 fallback для больших ответов (DNSSEC, AXFR zone transfer). DoH/DoT — DNS поверх HTTPS/TLS, но это компромисс ради приватности, не производительности.
HTTP/3 (QUIC) — Cloudflare, Google, Facebook, Fastly. На 2026 ~30% трафика топ-1000 сайтов идёт через QUIC. Mobile-сети особенно выигрывают от connection migration.
WebRTC — UDP для media (RTP/RTCP), DataChannel поверх SCTP-over-DTLS-over-UDP. Zoom, Google Meet, Discord voice, Apple FaceTime.
Online games — Counter-Strike, Fortnite, Valorant, Overwatch, PUBG. Все используют кастомные UDP-протоколы с app-layer reliability только для критичных событий (hit registration), всё остальное — fire-and-forget snapshots.
NTP, DHCP, mDNS, syslog — классика. Простые request-response или broadcast, помещаются в один пакет.
QUIC-based VPN — WireGuard поверх UDP, потому что TCP-over-TCP (классический OpenVPN-через-TCP) даёт катастрофический «TCP meltdown» при потерях.
Anti-patterns
UDP-пакеты больше MTU. Если payload > 1472 байт (MTU 1500 минус IP 20 минус UDP 8), IP-уровень фрагментирует пакет. Потеря любого фрагмента = потеря всего пакета. Плюс некоторые middleboxes (NAT, firewall) дропают фрагменты целиком. Best practice: держать payload ≤ 1400 байт, оставляя запас на VPN/IPv6/туннелирование.
«UDP в LAN надёжен». Нет. Switch buffer overflow при микробёрстах — реальная проблема для высокочастотных систем (мониторинг, telemetry, market data). На 1G/10G линках буферы маленькие, всплеск UDP-трафика может выкинуть 5-10% пакетов даже на «здоровой» сети.
Игнорирование congestion control. Naive UDP-приложение шлёт с полной скоростью — забивает bottleneck-роутер — пакеты дропаются — приложение не замедляется (нет ACK'ов) — продолжает забивать. Хуже всего: страдают TCP-соединения других пользователей, которые честно отступают по congestion-сигналам. Поэтому Linux имеет UDP rate limits, и провайдеры режут «UDP floods».
Отсутствие checksum. UDP checksum опциональна в IPv4 (обязательна в IPv6). Отключение «для скорости» = баги, которые невозможно поймать: один битфлип в payload — приложение получает мусор без warning'а.
Использование UDP source port для identity. NAT перепишет порт. Не привязывайте session ID к (src_ip, src_port) — используйте connection ID внутри payload, как делает QUIC.
Heartbeat реже 25 секунд. NAT-таблицы у домашних роутеров выкидывают UDP-сессии через 30с idle. Чтобы UDP-соединение жило долго, нужен keepalive каждые 15-25 секунд. WireGuard по умолчанию шлёт каждые 25с.
Когда НЕ использовать
File transfer, бэкапы, репликация БД. Любой потерянный байт = corrupted file. TCP без вариантов (или специализированный UDP-протокол вроде UDT/Aspera с тщательным error recovery — но это нишевые решения для очень специфичных сценариев типа CDN edge sync).
HTTP/1.1, HTTP/2, REST API. TCP — естественный выбор: запросы могут быть большими, ответы тоже, нужен reliable byte stream. HTTP/3 (QUIC) — отдельный разговор, но это не про «UDP лучше TCP», а про обход TCP head-of-line blocking на multiplexed streams.
Когда не контролируете оба конца. Если ваш сервер общается с произвольными клиентами через произвольные сети, и вы не можете гарантировать поддержку вашего custom UDP-протокола на стороне клиента, NAT и firewall — лучше TCP/443 (или HTTPS). UDP блокируется чаще, чем TCP/443.
Когда нет ресурсов на app-layer reliability. Если на проекте никто не хочет писать retry/ordering/congestion control руками — берите TCP. Сэкономленное время разработки стоит больше, чем выигрыш в latency.