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

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

В отличие от UDP, TCP гарантирует:

  • доставку всех данных;
  • отсутствие дубликатов;
  • сохранение порядка передачи;
  • контроль перегрузки сети;
  • управление скоростью передачи.

Именно благодаря этим свойствам TCP стал фундаментом Интернета.


TCP в модели OSI

TCP располагается на транспортном уровне (Layer 4).

Прикладной уровень
Транспортный уровень (TCP)
Сетевой уровень (IP)
Канальный уровень

Каждый уровень решает свою задачу.

  • IP отвечает только за доставку пакетов между узлами.
  • TCP строит поверх IP надежный канал передачи данных.

Можно сказать, что IP - это служба доставки писем, а TCP - сотрудник, который следит, чтобы письмо дошло полностью, в правильном порядке и без потерь.


Поток байт. Почему TCP не передает сообщения

Одно из самых важных свойств TCP - он не знает ничего о сообщениях приложения. TCP работает исключительно с непрерывным потоком байт (Byte Stream).

Например, приложение отправляет:

send("Hello")
send("World")

Разработчик может ожидать, что получатель увидит два сообщения:

Hello
World

На самом деле TCP гарантирует только получение последовательности байт:

HelloWorld

Получатель может прочитать данные как угодно:

HelloWorld

Hel
loWo
rld

Hello
World

H
e
lloWo
rld

Границы сообщений TCP не сохраняет. Именно поэтому большинство прикладных протоколов сами определяют формат сообщений.

Обычно используется один из способов:

  • длина сообщения;
  • специальный разделитель (\n);
  • фиксированный размер пакета.

Сегменты TCP

Хотя приложение видит поток байт, внутри TCP данные передаются сегментами (TCP Segment).

Например, приложение отправило:

20 КБ данных

TCP разобьет поток примерно так:

1460
1460
1460
1460
...

Каждый сегмент получает собственный TCP-заголовок. Размер сегмента определяется параметром MSS (Maximum Segment Size).

Важно понимать: Сегмент - это исключительно внутренняя единица передачи TCP. Приложение никогда не узнает, сколько сегментов использовалось.


Установка соединения

Перед передачей данных TCP выполняет процедуру Three-way Handshake.

SYN =>
<= SYN + ACK
ACK =>

После этого соединение считается установленным.

Разберем каждый пакет.

- SYN

Клиент сообщает: Хочу установить соединение.

Также отправляется начальный номер последовательности (Initial Sequence Number).

SYN + ACK

Сервер отвечает: Я согласен. Вот мой номер последовательности.

Одновременно подтверждает получение SYN клиента.

ACK

Клиент подтверждает получение SYN сервера. После этого обе стороны готовы обмениваться данными.


Логика передачи сообщений

Во время работы TCP использует несколько специальных флагов.

SYN

Используется только при установке соединения.

ACK

Практически каждый пакет после установки соединения содержит ACK. Он подтверждает получение данных.

FIN

Сообщает: Я больше ничего отправлять не буду.

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

RST

Используется для аварийного завершения соединения.

Например:

  • приложение завершилось;
  • порт закрыт;
  • произошла ошибка.

Получив RST, другая сторона немедленно уничтожает соединение.


Разрыв соединения

Закрытие TCP также требует обмена сообщениями.

FIN =>
<= ACK
<= FIN
ACK =>

Получается четырехэтапное закрытие (Four-way Handshake).

Почему четыре сообщения? Потому что каждое направление соединения закрывается независимо.

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


Нумерация данных

Каждый байт в TCP имеет собственный порядковый номер (Sequence Number).

Например, передается строка: HelloWorld

H 1
e 2
l 3
l 4
o 5
W 6
o 7
r 8
l 9
d 10

Если сегмент содержит Hello, то он имеет:

SEQ = 1
Length = 5

Следующий сегмент начинается с:

SEQ = 6

То есть номер относится к первому байту сегмента, а не к самому сегменту.

Это позволяет:

  • обнаруживать потери;
  • собирать поток в правильном порядке;
  • повторно отправлять только потерянные данные.

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

После получения данных принимающая сторона отправляет подтверждение.

Например:

SEQ = 1
Length = 1000

получатель отвечает:

ACK = 1001

Это означает: Все байты до 1000 включительно получены.

ACK всегда содержит номер следующего ожидаемого байта.


Формат заголовка TCP

Каждый TCP-сегмент начинается с заголовка.

Основные поля:

  • Source Port Порт отправителя
  • Destination Port Порт получателя
  • Sequence Number Номер первого байта сегмента
  • Acknowledgment Number Следующий ожидаемый байт
  • Data Offset Размер заголовка
  • Flags SYN, ACK, FIN, RST, PSH, URG и другие
  • Window Size Размер окна приема
  • Checksum Контрольная сумма
  • Urgent Pointer Используется совместно с URG
  • Options MSS, Window Scale, SACK, Timestamp и другие

Минимальный размер заголовка составляет 20 байт. При наличии опций он может увеличиваться до 60 байт.


Управление потоком (Flow Control)

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

Например, сервер способен принимать только 100 КБ/с, а клиент передает 10 МБ/с.

Без ограничений буфер получателя быстро переполнится. Для решения этой проблемы TCP использует механизм Flow Control.

Получатель сообщает: Window = 32768

Это означает: Я сейчас готов принять еще 32768 байт.

После заполнения буфера окно уменьшается. Когда буфер освобождается, окно снова увеличивается.

Если окно становится равным нулю, отправитель временно прекращает передачу данных и периодически отправляет специальные probe-пакеты, чтобы узнать, освободилось ли место.

Важно понимать, что Flow Control и Congestion Control - разные механизмы:

  • Flow Control защищает получателя от переполнения буфера.
  • Congestion Control защищает сеть от перегрузки.

Source: Orkhan Alishov's notes