usepoint
menu

Протокол TCP: надёжная доставка данных

TCP превращает нестабильную сеть в предсказуемый поток байтов: сначала договаривается, потом аккуратно передаёт данные, подтверждает их получение, переотправляет потерянное и сам регулирует скорость, чтобы связь не развалилась.

tcp

Содержание

В предыдущих статьях мы говорили, что:

  • IP отвечает за доставку пакетов «из сети в сеть» по IP-адресам;
  • TCP - протокол поверх IP, который делает доставку надёжной и упорядоченной.

Что вообще такое TCP-соединение

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

TCP работает между двумя “концами”, каждый конец описывается парой IP-адрес + номер порта

Соединение описывается четверкой: IP_клиента, порт_клиента, IP_сервера, порт_сервера.

Например:

  • клиент: 91.76.65.216:50123,
  • сервер: 203.0.113.10:443,
  • соединение:
    91.76.65.216:50123 ↔ 203.0.113.10:443.

Важно знать, что TCP запоминает состояние соединения - что уже отправили, что уже получили, где ждём подтверждения и т.д.

Установление соединения (3-way handshake)

TCP - протокол с установлением соединения.

Прежде чем обмениваться данными, стороны делают «рукопожатие»:
договариваются о том, что оба готовы, и согласуют номера для отслеживания данных.

Нам нужно ввести пару терминов:

  • TCP-сегмент - «кусок» данных TCP.
    IP передаёт пакеты, TCP - сегменты. На проводе это одно и то же «обёрнутое» по слоям сообщение, просто на уровне TCP его называют сегментом.
  • Порядковый номер (sequence number) - номер первого байта данных в сегменте. Нужен, чтобы восстановить порядок и понять, до какого места данные уже дошли.
  • Подтверждение (ACK, acknowledgment) - ответ, который говорит:
    «я успешно получил данные до такого-то порядкового номера».

Рукопожатие «3-way handshake» называется так, потому что состоит из трёх шагов:

Шаг 1. Клиент: SYN

  • Клиент хочет установить соединение с сервером.
  • Он отправляет специальный сегмент с флажком SYN (synchronize - «синхронизировать»).
  • В этом сегменте клиент не отправляет полезные данные, при этом выбирает начальный порядковый номер (например, X).

Смысл следующий: «Привет, сервер. Я хочу открыть соединение. Мой стартовый порядковый номер = X».

Шаг 2. Сервер: SYN + ACK

Сервер получил SYN, готов общаться:

  • он отвечает сегментом, где два флага:
    • SYN - сервер тоже сообщает свой начальный порядковый номер (например, Y),
    • ACK - подтверждает, что получил SYN-клиента.

Подтверждение устроено так:

  • сервер говорит: «я принял твой номер X, и подтверждаю всё до X+1» (так устроен TCP-протокол).

Смысл следующий: «Привет, клиент. Я согласен, соединение открываем. Твой стартовый номер X я принял, мой стартовый номер = Y».

Шаг 3. Клиент: ACK

Клиент получает ответ (SYN+ACK) и отправляет третий сегмент:

  • с флагом ACK - подтверждает, что принял стартовый номер сервера Y.

Смысл следующий: «Ок, твой номер Y я принял. Давай обмениваться данными».

После этого соединение считается установленным. Обе стороны знают начальный порядковый номер друг друга и что собеседник действительно жив и готов.

Почему так сложно, а не “просто отправили данные”?

  • Нужно защититься от старых/заблудившихся сегментов в сети: начальные номера делают каждое соединение «уникальным по времени».
  • Нужно убедиться, что оба конца действительно готовы (не просто адрес существует, а приложение реально слушает порт и ответило).

Надёжная доставка и порядок сообщений

Теперь, когда соединение установлено, приложения могут отправлять данные.

TCP делает из сети надёжный двусторонний поток байтов, как будто между приложениями протянут провод, по которому всё доходит и в правильном порядке.

На самом деле под капотом приложение отдаёт данные в TCP потоком байтов. TCP разбивает этот поток на сегменты - кусочки удобного размера, а каждому сегменту TCP даёт порядковый номер (sequence number) - номер первого байта в этом сегменте и отправляет сегмент через IP.

На принимающей стороне TCP получает сегменты не обязательно по порядку (сеть может менять порядок). Далее смотрит на их sequence number. Это как страницы книги с номерами - если пришли 3, 1, 2 - мы всё равно сможем собрать 1-2-3. Складывает сегменты в буфер (временное хранилище в памяти), пока не получит все кусочки до определённого номера и не сможет отдать приложению ровный поток байтов в правильном порядке.

Как TCP понимает, что всё дошло

Здесь вступают в игру подтверждения (ACK).

Принцип простой - принимающая сторона периодически отправляет отправителю сообщение: «Я корректно получил все байты до номера N,
можешь считать их принятыми».

Это называется кумулятивное подтверждение - не нужно подтверждать каждый сегмент отдельно, а можно сказать: «до N - всё ок», даже если было несколько сегментов.

Пример:

  1. Клиент отправил три сегмента:
    • 0–999,
    • 1000–1999,
    • 2000–2999.
  2. Сервер всё получил и прислал ACK:
    • «принял до 3000».
  3. Клиент может выкинуть эти данные из буфера отправленных (они уже гарантированно доставлены) и дальше отправлять новые байты с номерами 3000+.

Если что-то не дошло - перейдём к следующему пункту.

Контроль ошибок и повторная отправка

В реальной сети пакеты могут теряться, приходить с ошибками или приходить в разном порядке. TCP должен с этим справляться.

Разберём три механизма:

  1. Проверка целостности (контроль ошибок).
  2. Повторная отправка (retransmission).
  3. Регулировка скорости (чтобы не «забить» сеть).

1) Проверка целостности (контроль ошибок)

Каждый TCP-сегмент содержит контрольную сумму.

Контрольная сумма - это число, которое TCP считает по содержимому сегмента (по данным и заголовку). Когда сегмент приходит на другую сторону TCP заново считает контрольную сумму и сравнивает с той, что пришла.

Если суммы не совпадают, значит, по пути в сети данные исказились (битфлип, помехи и т.п.). Такой сегмент отбрасывается (считается испорченным) и не подтверждается (не включается в ACK).

Отправитель, не получив подтверждения, затем сделает повторную отправку.

2) Повторная отправка (retransmission)

Как TCP понимает, что нужно переотправить данные?

Есть несколько сигналов:

  1. Таймаут (timeout)
    • Когда TCP отправляет сегмент, он запоминает время.
    • Если прошло довольно много времени, а подтверждения (ACK) всё нет, то TCP считает, что сегмент потерялся и отправляет этот сегмент повторно.
  2. ”Странные” подтверждения
    Существуют механизмы, когда принимающая сторона может намекнуть: «Я получил вот до такого номера, но дальше у меня дырка». В простом виде это выглядит как серия одинаковых ACK (дубликатов) с одним и тем же номером - отправитель понимает: «после этого места что-то не доходит».

В обоих случаях сегмент будет отправлен ещё раз. Это продолжается, пока либо данные дойдут, либо соединение не будет признано мёртвым.

3) Регулировка скорости (управление потоком и перегрузкой)

Если отправитель будет «лить» данные с максимальной скоростью сеть может не успевать, буферы маршрутизаторов/получателя переполнятся и начнутся массовые потери пакетов.

Поэтому TCP включает саморегулировку. Если всё идёт гладко, потерь мало, то TCP постепенно увеличивает объем данных, которые можно отправлять, не дожидаясь подтверждений (это называют «окном»). 

Если начались потери и таймауты, то TCP снижает скорость (уменьшает окно) и отправляет меньше неподтверждённых данных одновременно.

Интуитивная аналогия выглядит следующим образом. Представьте, что вы разговариваете по плохой связи. Если собеседник всё хорошо слышит, то вы говорите быстрее, более длинными фразами, а если же он часто переспрашивает, то начинаете говорить медленнее и короткими фразами.