Когда данные летят от вашего сервиса к соседнему, за доставку между двумя процессами отвечает транспортный слой. И на этом слое есть выбор из двух протоколов: TCP и UDP. Они решают одну и ту же задачу — донести байты от отправителя к получателю — но с прямо противоположной философией. TCP похож на заказное письмо с уведомлением о вручении: медленнее, зато точно дойдёт и в правильном порядке. UDP — как открытка, брошенная в почтовый ящик: улетела мгновенно, но дошла ли она и в каком порядке — никто не гарантирует.
Разберёмся, как работает каждый, и — главное — когда backend выбирает тот или другой. Потому что выбор не про «какой лучше», а про то, что важнее для конкретной задачи: надёжность или скорость.
TCP: доставка с гарантией
TCP (Transmission Control Protocol) построен вокруг понятия соединения. Прежде чем отправить хоть один байт полезных данных, две стороны договариваются, что они готовы общаться. И дальше протокол берёт на себя кучу забот, чтобы данные дошли целиком, без потерь и в том порядке, в котором были отправлены.
Что именно гарантирует TCP:
- Доставку. Если пакет потерялся по дороге, TCP это заметит и отправит его заново. С точки зрения вашего кода данные просто «дошли» — механика переотправки скрыта внутри.
- Порядок. Пакеты в сети могут прийти вперемешку — разными путями, с разной задержкой. TCP нумерует их и на приёмной стороне собирает обратно в исходной последовательности.
- Целостность. Каждый кусок проверяется контрольной суммой; битые данные отбрасываются и запрашиваются заново.
За всё это платят задержкой и накладными расходами: нужны номера, подтверждения, буферы. Но для большинства backend-задач это ровно то, что нужно — вы отправили запрос и уверены, что он дойдёт таким, каким был.
Three-way handshake: как открывается соединение
Установка TCP-соединения — это короткий обмен из трёх сообщений, его называют three-way handshake (тройное рукопожатие). На пальцах это выглядит как знакомство по телефону:
- SYN — клиент звонит: «Привет, я хочу с тобой поговорить, слышишь меня?»
- SYN-ACK — сервер отвечает: «Слышу тебя. А ты меня слышишь?»
- ACK — клиент подтверждает: «Слышу. Начинаем».
После этих трёх шагов обе стороны убедились, что канал работает в обе стороны, и договорились о стартовых номерах, по которым будут собирать пакеты. Только теперь пойдут реальные данные.
Здесь прячется важная для backend деталь: рукопожатие стоит один round-trip — один полный оборот «туда-обратно» до сервера, прежде чем уйдёт первый полезный байт. Этот оборот называют RTT (round-trip time). Если сервер за океаном, один только RTT — это десятки миллисекунд, и они добавляются к каждому новому соединению ещё до того, как вы отправили запрос. Отсюда практический вывод: соединения дорого открывать, поэтому их переиспользуют — не открывают новое на каждый запрос, а держат пул готовых. Подробнее эту механику разбираем в статье про соединения и их переиспользование.
Подтверждения и переотправка
Как TCP понимает, что пакет дошёл? По подтверждениям. Отправив данные, TCP ждёт от другой стороны ACK — «получил». Если ACK не пришёл за отведённое время, протокол считает пакет потерянным и отправляет его заново.
Грубая аналогия — разговор по рации: сказал фразу и ждёшь «принял». Не услышал подтверждения — повторяешь.
Отправитель Получатель
|-- пакет 1 -------->|
|<----- ACK 1 -------|
|-- пакет 2 -------->| (потерялся!)
| ...ждём... |
|-- пакет 2 (повтор)>|
|<----- ACK 2 -------|
Заодно TCP следит, чтобы не завалить сеть и получателя данными: если пакеты начинают теряться, он сбавляет темп, а на спокойной линии — разгоняется. Это называется контролем перегрузки, и именно поэтому скорость TCP-соединения не постоянна, а подстраивается под состояние сети. Что делать, когда соединение всё же рвётся или тормозит, — тема статьи про надёжность передачи.
UDP: быстро и без обещаний
UDP (User Datagram Protocol) — полная противоположность. Никакого соединения, никаких рукопожатий, никаких подтверждений. Взял данные, приписал порт получателя, выбросил в сеть — и забыл. Дошло ли, в каком порядке, дошло ли вообще — протокол не проверяет и не сообщает.
Звучит как недостаток, но именно в этом сила UDP:
- Мгновенный старт. Нет рукопожатия — нет лишнего round-trip. Первый пакет с данными уходит сразу.
- Минимум накладных расходов. Заголовок крошечный, состояние соединения нигде не хранится.
- Свобода поведения. Приложение само решает, что делать с потерями: переспросить, подставить заглушку или просто пропустить.
Плата за это — все гарантии теперь на вашей совести. Если пакет потерялся, UDP об этом не узнает. Если два пакета пришли в обратном порядке — так они и лягут. Поэтому UDP хорош там, где потеря отдельного кусочка либо терпима, либо приложение умеет её обработать само.
Когда что выбирать
Правило простое: важна целостность каждого байта — TCP; важнее скорость и свежесть, а потери терпимы — UDP.
TCP выбирают, когда данные нельзя терять:
- HTTP и весь веб — страница или ответ API должны прийти целиком, без пропущенных кусков.
- Базы данных — потерять половину запроса или ответа недопустимо.
- Передача файлов, платежи, почта — всё, где важна каждая единица данных и её порядок.
UDP выбирают, когда важнее не опоздать:
- DNS-запросы — крошечный вопрос и такой же ответ; потерялся — просто спросим ещё раз, это дешевле, чем городить соединение.
- Видеозвонки и голос — если один кадр потерялся, ждать его переотправку бессмысленно: пока он дойдёт, разговор уже уедет вперёд. Лучше пропустить и показать следующий.
- Онлайн-игры, метрики, телеметрия — поток частых обновлений, где важна свежая величина, а не каждое отдельное значение. Потерянную метрику перекроет следующая через секунду.
Заметьте закономерность: TCP — там, где данные самоценны; UDP — там, где данные быстро устаревают и опоздавший пакет уже никому не нужен.
QUIC: UDP с надёжностью сверху
У этой картины есть современное продолжение. Долгое время выбор был жёстким: хочешь гарантии — плати рукопожатием TCP; хочешь скорость — бери UDP и разбирайся с потерями сам. QUIC ломает эту дилемму.
QUIC — это протокол, который работает поверх UDP, но сам реализует всё, чем силён TCP: подтверждения, переотправку потерянного, порядок, шифрование. Получается «UDP снаружи, надёжность внутри». Зачем так? Чтобы обойти ограничения, зашитые в TCP: например, QUIC умеет устанавливать защищённое соединение быстрее, за меньшее число round-trip, и не спотыкается, когда теряется один из параллельных потоков данных.
Именно на QUIC построен HTTP/3 — новейшая версия протокола, на который постепенно переходит веб. Так что UDP, который вроде бы «без гарантий», оказался фундаментом для самого надёжного и быстрого HTTP на сегодня. Как менялся HTTP от версии к версии — в статье про версии HTTP.
Где это применяется
Для backend разница TCP и UDP — это в первую очередь про то, где искать причину проблемы и почему код устроен так, а не иначе.
- Пул соединений — это про стоимость TCP-рукопожатия. Когда вы настраиваете пул к базе или HTTP-клиенту, вы платите за то, чтобы не делать three-way handshake на каждый запрос. Понимая, что установка стоит round-trip, вы понимаете, зачем вообще нужен пул.
- «Медленно с первого запроса, потом быстро» — часто это рукопожатие. Первый вызов к холодному сервису включает установку соединения (а с HTTPS — ещё и TLS поверх); последующие идут по уже открытому каналу.
- DNS «иногда не отвечает» — это UDP-природа. DNS-запрос по UDP может потеряться без всякого сигнала; клиент просто повторяет его по таймауту. Если таймаут большой, задержка становится заметной.
Где спотыкаются начинающие:
- Считают UDP «сломанным TCP». UDP не хуже — он про другое. Отсутствие гарантий здесь не баг, а сознательный размен на скорость.
- Думают, что UDP всегда быстрее. На чистой линии — да, но при потерях приложению всё равно приходится что-то делать с пропавшими данными, и наивная переотправка поверх UDP легко оказывается медленнее TCP.
- Открывают новое TCP-соединение на каждый запрос. Каждый раз платится round-trip рукопожатия. Пул соединений существует ровно затем, чтобы этого не делать.
- Путают «соединение» и «сессию приложения». TCP-соединение — это транспортный канал; пользовательская сессия с токенами и куками живёт слоем выше и к handshake отношения не имеет.
Что учить дальше
TCP и UDP — это транспорт, но у него есть соседи по слоям, которые стоит понимать вместе. Ниже — адресация: IP-адреса и порты, которые как раз и определяют, до какой машины и какого процесса дойдёт TCP- или UDP-пакет. Прикладной слой поверх TCP — это DNS (который, кстати, живёт на UDP) и версии HTTP, где QUIC доводит историю до HTTP/3. А чтобы понять, почему соединения переиспользуют и что происходит при обрывах, — статьи про соединения и надёжность передачи.