Когда данные летят от вашего сервиса к соседнему, за доставку между двумя процессами отвечает транспортный слой. И на этом слое есть выбор из двух протоколов: TCP и UDP. Они решают одну и ту же задачу — донести байты от отправителя к получателю — но с прямо противоположной философией. TCP похож на заказное письмо с уведомлением о вручении: медленнее, зато точно дойдёт и в правильном порядке. UDP — как открытка, брошенная в почтовый ящик: улетела мгновенно, но дошла ли она и в каком порядке — никто не гарантирует.

Разберёмся, как работает каждый, и — главное — когда backend выбирает тот или другой. Потому что выбор не про «какой лучше», а про то, что важнее для конкретной задачи: надёжность или скорость.

TCP: доставка с гарантией

TCP (Transmission Control Protocol) построен вокруг понятия соединения. Прежде чем отправить хоть один байт полезных данных, две стороны договариваются, что они готовы общаться. И дальше протокол берёт на себя кучу забот, чтобы данные дошли целиком, без потерь и в том порядке, в котором были отправлены.

Что именно гарантирует TCP:

  • Доставку. Если пакет потерялся по дороге, TCP это заметит и отправит его заново. С точки зрения вашего кода данные просто «дошли» — механика переотправки скрыта внутри.
  • Порядок. Пакеты в сети могут прийти вперемешку — разными путями, с разной задержкой. TCP нумерует их и на приёмной стороне собирает обратно в исходной последовательности.
  • Целостность. Каждый кусок проверяется контрольной суммой; битые данные отбрасываются и запрашиваются заново.

За всё это платят задержкой и накладными расходами: нужны номера, подтверждения, буферы. Но для большинства backend-задач это ровно то, что нужно — вы отправили запрос и уверены, что он дойдёт таким, каким был.

Three-way handshake: как открывается соединение

Установка TCP-соединения — это короткий обмен из трёх сообщений, его называют three-way handshake (тройное рукопожатие). На пальцах это выглядит как знакомство по телефону:

  1. SYN — клиент звонит: «Привет, я хочу с тобой поговорить, слышишь меня?»
  2. SYN-ACK — сервер отвечает: «Слышу тебя. А ты меня слышишь?»
  3. 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. А чтобы понять, почему соединения переиспользуют и что происходит при обрывах, — статьи про соединения и надёжность передачи.