TCP — надёжная доставка
Протокол, который превращает ненадёжную сеть в аккуратный поток данных: всё дойдёт, ничего не потеряется и не перепутается.
- как TCP устанавливает и закрывает соединение;
- за счёт чего он обнаруживает потери и восстанавливает порядок;
- как он подстраивает скорость под получателя и под сеть и чем отличается от UDP.
5.1Что обещает TCP
TCP (Transmission Control Protocol) работает поверх того же ненадёжного IP, но даёт программе совсем другие условия:
- Гарантия доставки. Каждый отправленный байт должен быть подтверждён. Нет подтверждения — данные отправляются заново.
- Правильный порядок. Получатель расставляет пришедшие части по номерам, даже если они пришли вперемешку.
- Без дубликатов. Пришедшее дважды отбрасывается.
- Управление потоком. Отправитель не шлёт больше, чем получатель готов принять.
- Управление перегрузкой. Отправитель снижает темп, если сеть не справляется.
Для программы TCP выглядит как непрерывный поток байтов в обе стороны — как труба, в которую с одного конца заливают, а с другого получают то же самое. Как именно поток нарезается на пакеты, программу не касается. Одна порция данных TCP вместе с заголовком называется сегментом.
Вы отправляете многотомную рукопись частями. Каждая бандероль пронумерована, получатель расписывается за каждую, а если расписка не пришла — вы высылаете копию. На другом конце тома расставляют по номерам. Это дольше обычной открытки, зато рукопись дойдёт целиком.
5.2Установление соединения: тройное рукопожатие
Прежде чем передавать данные, стороны должны договориться. TCP делает это за три шага — отсюда название трёхстороннее рукопожатие (three-way handshake):
| Направление | Сегмент | Смысл |
|---|---|---|
| Клиент → Сервер | SYN | «Хочу соединиться. Мой начальный номер — 1000» |
| Сервер → Клиент | SYN + ACK | «Согласен. Мой начальный номер — 5000, жду от тебя 1001» |
| Клиент → Сервер | ACK | «Принял. Жду от тебя 5001» |
После этого соединение установлено, и можно передавать данные.
Зачем три шага, а не один? Каждая сторона должна убедиться в двух вещах: что собеседник её слышит и что она слышит собеседника. Заодно стороны обмениваются начальными номерами последовательности — они выбираются случайно, чтобы посторонний не мог подделать сегмент чужого соединения.
Рукопожатие стоит времени: один обмен «туда-обратно» (его обозначают RTT) ещё до первого байта полезных данных. На расстоянии в полмира это 150–300 миллисекунд — одна из причин, по которой появились более быстрые протоколы вроде QUIC.
5.3Номера, подтверждения и повторная отправка
Вся надёжность TCP держится на двух числах в каждом сегменте:
- Номер последовательности (SEQ) — порядковый номер первого байта в этом сегменте.
- Номер подтверждения (ACK) — «я получил всё до этого места, жду байт с таким-то номером».
Подтверждения накопительные: ACK 3001 означает, что получены все байты до 3000 включительно. Если какой-то сегмент пропал, получатель продолжает повторять один и тот же номер — так отправитель узнаёт, где образовалась дыра.
| Механизм | Как работает |
|---|---|
| Повтор по таймеру | Отправитель ждёт подтверждения ограниченное время. Не дождался — отправляет сегмент заново. Время ожидания подстраивается под реальную задержку в сети. |
| Быстрый повтор | Три одинаковых подтверждения подряд — верный признак потери. Отправитель повторяет сегмент сразу, не дожидаясь таймера. |
| Выборочные подтверждения (SACK) | Получатель сообщает не только «жду с 3001», но и «при этом у меня уже есть 4001–6000». Отправитель повторяет только потерянный кусок. |
5.4Управление скоростью: окно и перегрузка
Слать данные с максимальной скоростью нельзя: можно «захлебнуть» получателя или сеть. TCP ограничивает себя сразу с двух сторон.
Управление потоком — не перегрузить получателя
В каждом сегменте получатель сообщает размер своего окна приёма: сколько байтов он ещё готов принять в буфер. Отправитель не имеет права отправить больше. Если программа-получатель не успевает разбирать данные, окно сжимается вплоть до нуля, и передача приостанавливается.
Управление перегрузкой — не перегрузить сеть
Сеть о своей загрузке напрямую не сообщает, поэтому TCP нащупывает допустимую скорость сам:
1. Медленный старт. Соединение начинает с небольшого объёма и удваивает его после каждого
успешного обмена — скорость растёт очень быстро.
2. Предотвращение перегрузки. После определённого порога рост становится плавным и
осторожным.
3. Реакция на потерю. Потерянный пакет считается сигналом перегрузки: скорость резко
снижается, и подбор начинается заново.
Конкретные правила называются алгоритмами управления перегрузкой. Самые распространённые сегодня — CUBIC (используется по умолчанию в большинстве систем) и BBR, который ориентируется не на потери, а на измеренную пропускную способность и задержку.
Управление перегрузкой — причина, по которой интернет не «падает» под нагрузкой. Миллиарды соединений добровольно притормаживают, когда каналу тяжело, и делят его между собой более-менее честно.
5.5Заголовок TCP и флаги
Заголовок TCP занимает от 20 до 60 байт. Кроме портов, номеров и окна, в нём есть набор однобитных флагов, которые задают смысл сегмента:
| Флаг | Значение |
|---|---|
| SYN | Запрос на установление соединения. Встречается только в рукопожатии. |
| ACK | Сегмент содержит подтверждение. Установлен почти во всех сегментах, кроме самого первого. |
| FIN | «Я закончил передачу» — вежливое завершение соединения. |
| RST | Немедленный разрыв: порт закрыт, соединение не существует или произошла ошибка. |
| PSH | Просьба сразу передать данные программе, не накапливая в буфере. |
| URG | Пометка срочных данных. Сегодня почти не используется. |
В рукопожатии стороны также согласуют дополнительные опции: максимальный размер сегмента (MSS — обычно 1460 байт для Ethernet), масштабирование окна для быстрых каналов, поддержку SACK и метки времени.
5.6Завершение соединения
Соединение TCP двустороннее, и каждая сторона закрывает свою половину отдельно. Поэтому обычное завершение состоит из четырёх сегментов:
| Направление | Сегмент | Смысл |
|---|---|---|
| Клиент → Сервер | FIN | «Я всё передал» |
| Сервер → Клиент | ACK | «Понял» (сервер ещё может досылать свои данные) |
| Сервер → Клиент | FIN | «Я тоже закончил» |
| Клиент → Сервер | ACK | «Понял, закрываем» |
После этого сторона, закрывшая соединение первой, ещё некоторое время помнит о нём (состояние TIME_WAIT), чтобы запоздавшие сегменты не попали в новое соединение с теми же портами.
Существует и аварийный способ — сегмент с флагом RST. Он обрывает соединение мгновенно и без подтверждений. Именно RST вы получаете в ответ, когда пытаетесь подключиться к порту, который никто не слушает: «соединение отклонено».
5.7TCP против UDP
| Свойство | TCP | UDP |
|---|---|---|
| Соединение | Устанавливается рукопожатием | Не нужно |
| Доставка | Гарантируется, потери повторяются | Не гарантируется |
| Порядок данных | Сохраняется | Не сохраняется |
| Что видит программа | Непрерывный поток байтов | Отдельные сообщения |
| Управление скоростью | Есть: поток и перегрузка | Нет |
| Заголовок | 20–60 байт | 8 байт |
| Задержка перед отправкой | Один обмен на рукопожатие | Нет |
| Типичные применения | Сайты, почта, файлы, удалённый доступ | DNS, звонки, игры, стриминг, QUIC |
Раз TCP отдаёт данные строго по порядку, один потерянный сегмент задерживает всё, что пришло после него, — пока не дойдёт повтор. Это называется блокировкой начала очереди (head-of-line blocking). Для загрузки файла это не страшно, а для страницы из сотни независимых картинок — обидно. Запомните этот термин: он объяснит, зачем понадобился HTTP/3.
- Не существует «лучшего» транспорта. TCP выбирают, когда важно получить всё и без ошибок; UDP — когда важнее скорость и свежесть данных, либо когда приложение хочет управлять надёжностью самостоятельно.