Прямой ответ
TCP и UDP — два контракта доставки поверх IP, и выбор между ними — про что хуже: потеря или задержка. TCP (RFC 9293) — надёжный поток: рукопожатие (SYN/SYN-ACK/ACK — соединение перед данными), подтверждения и ретрансмиты потерь, порядок байтов гарантирован, перегрузочный контроль (отправка притормаживает при потерях) — цена: задержки на установку и head-of-line blocking (потерянный пакет задерживает все следующие за ним, даже уже полученные). UDP (RFC 768) — датаграммы без гарантий: нет соединения, порядка, ретрансмитов — «бросил и забыл»; зато ноль накладных на установку и никаких пауз ожидания. Правило выбора: данные, где потеря недопустима, а задержка терпима (файлы, API-запросы, сообщения) — TCP; данные, где свежие важнее полных (голос, видео, игра, телеметрия) — UDP со своей логикой надёжности поверх (или без неё: потерянный кадр видео просто не показывают). Третий игрок — QUIC/HTTP3: UDP как транспорт с надёжностью и шифрованием поверх, без TCP-ного head-of-line между потоками — будущее веба, уже работающее в крупных сетях.
TCP: за что платим надёжностью
Механика, которую стоит представлять: рукопожатие — 1 RTT до первого байта (плюс TLS — ещё 1–2 RTT: «медленный первый запрос» к удалённому API — часто именно establishment, не обработка; keep-alive соединения в HTTP-клиентах экономят эти RTT на каждом запросе — httpx-пул); порядок и ретрансмиты — получатель подтверждает (ACK), отправитель повторяет неподтверждённое: доставка гарантирована, но потеря одного пакета = пауза для всех данных за ним (head-of-line — критично в realtime: поэтому игры не на TCP); перегрузочный контроль (slow start, congestion window) — TCP нащупывает доступную полосу, стартуя медленно: короткие соединения в медленном старте не успевают разогнаться (отсюда «мелкие частые запросы медленнее одного большого»); закрытие — FIN-обмен и состояния TIME_WAIT (тысячи коротких соединений исчерпывают порты — «cannot assign requested address» под нагрузкой: лечится пулом соединений/keep-alive, а не sysctl-хаками в первую очередь). Для разработчика следствия просты: TCP даёт поток байтов (границы «сообщений» — ваша забота: фрейминг в протоколе — WebSocket-фреймы решают это на своём уровне), таймауты обязательны (дефолтов «навсегда» не бывает в продакшен-клиентах).
UDP: когда гарантии мешают
UDP — 8 байт заголовка, отправка без состояния: пакет может прийти дублированным, повреждённым, в другом порядке или не прийти — и приложение об этом никто не уведомит (кроме ошибки отправки). Где это приемлемо: DNS — запрос-ответ в одну датаграмму (TCP-рукопожатие удвоило бы время каждого резолва — DNS-статья; большие ответы — EDNS/фрагментация, а TCP — fallback); голос/видео — опоздавший пакет бесполезен (кадр уже показан), ретрансмит только вредит — кодеки сами скрывают потери; игры — состояние мира важнее истории: свежий снапшот позиции заменяет утраченные; телеметрия/метрики (statsd-паттерн) — потеря 1% точек статистики не катастрофа; VPN (WireGuard — UDP с криптой и надёжностью своего уровня: туннель поверх TCP при потерях страдает дважды). Цена свободы — своя логика поверх: нумерация пакетов, детект дублей, FEC/интерполяция потерь, свой контроль темпа (UDP не тормозит сам — «вежливый» протокол реализуется приложением; агрессивный UDP-флуд — причина, по которой провайдеры и фаерволы режут/ограничивают UDP). MTU-ограничение: датаграмма >~1472 байт фрагментируется на IP-уровне (или теряется при PMTU-проблемах) — крупные сообщения режьте в приложении.
QUIC: UDP с гарантиями
HTTP/3 переносит транспорт в userspace поверх UDP (RFC 9000): нет TCP-рукопожатия (криптографическое рукопожатие TLS 1.3 встроено в транспорт — 1 RTT, а при повторном подключении 0-RTT); нет head-of-line между потоками (потеря в потоке A не тормозит поток B — внутри одного TCP-соединения HTTP/2 страдал именно этим); миграция соединений (идентификатор соединения вместо 4-tuple — смена сети WiFi→LTE не рвёт сессию). Цена: реализация в userspace дороже CPU ядра, UDP блокируется/режется в части корпоративных сетей (fallback на HTTP/2 обязателен), средняя зрелость инструментов отладки ниже. Для прикладного разработчика QUIC сегодня — «включено по умолчанию» в браузерах и CDN; писать своё поверх QUIC стоит для мобильных realtime-сценариев ( aioquic для Python), где выигрыши транспорта осязаемы.
Практический сценарий: выбор транспорта для фич
Продукт: веб-приложение + realtime-уведомления + загрузка больших отчётов. Разбор по фичам: API-запросы — HTTP/1.1-2 поверх TCP (доставка обязательна, задержка запроса терпима; HTTP/3 включается CDN'ом — бонус без кода); уведомления в браузере — WebSocket (TCP с фреймингом — сообщения не должны теряться/дублироваться; практика WebSocket: при обрыве — переподключение и дозапрос пропущенного, надёжность обеспечивается приложением поверх потока); живое состояние (курсоры, presence) — если появится: частые мелкие обновления, устаревание важнее полноты — UDP/WebTransport-кандидат, но в браузере WebTransport молод — старт с WebSocket и деградацией частоты; выгрузка отчёта 2 ГБ — TCP (HTTP-скачивание с докачкой Range-запросами: потеря — ретрансмит, целость критична; UDP-кастом здесь — изобретение своего TCP). Итог сценария: три транспорта под три профиля надёжность/задержка, и ни одной самописной «надёжной доставки поверх UDP» — классическая ошибка, стоящая месяцев разработки там, где хватило TCP.
FAQ
Почему мой UDP-трафик теряется, а TCP — нет? TCP ретрансмитит потери (они есть у всех — вопрос видимости); UDP-потери видны как «не дошло». Отдельная причина UDP-специфичных потерь — шейпинг/блокировки: провайдеры и фаерволы ограничивают UDP агрессивнее (исторически флуд-трафик), корпоративные сети режут не-DNS UDP целиком — тестировать с нескольких сетей (диагностика).
Можно ли сделать надёжную доставку поверх UDP самому? Технически да (нумерация, ACK, ретрансмиты, контроль перегрузки), практически — вы пишете свой TCP/QUIC со своими багами; оправдано только при специфичных требованиях (частичная надёжность, приоритеты потоков) — и тогда брать готовое (QUIC-библиотеки, ENet, KCP), а не изобретать.
Что быстрее: TCP или UDP? UDP — меньше накладных (нет установления/подтверждений), но «быстрее» для приложения означает «доставляет актуальное быстрее» в lossy-сетях; для целостных данных TCP быстрее по полезному результату (доставляет, а не «отправляет»). В чистых сетях с малым RTT разница для коротких запросов — один RTT рукопожатия (keep-alive и QUIC его убирают).
SCTP заслуживает внимания? Ниша: мультistreaming с частичной надёжностью и multi-homing (телеком, WebRTC data channels используют SCTP поверх DTLS); в обычной веб-разработке почти не встречается — QUIC занял его концептуальное место в вебе.
Источники: RFC 9293 (TCP), RFC 9000 (QUIC), RFC 1035 (DNS) — ссылки в шапке.