
Перед вами детальный разбор TCP — движущей силы интернета, в котором мы шаг за шагом рассмотрим принципы этой технологии на подробных примерах.
Интернет — невероятное изобретение. Людей от него за уши не оттащишь. Вот только есть у этого изобретения проблемы с надёжностью — пакеты теряются, каналы перегружаются, биты путаются, а данные повреждаются. Ох, какой же опасный мир! (Буду писать в духе Крамера).
Хорошо, почему же тогда наши приложения вот так просто работают? Если вы выводили своё приложение в сеть, то процесс вам знаком: socket()/bind() здесь, accept() там, возможно, connect() вон там и, вуаля — данные надёжно текут в обе стороны упорядоченным и целостным потоком.
Сайты (HTTP), сервисы e-mail (SMTP) или удалённый доступ (SSH) — всё это построено на основе TCP и просто работает.
Почему TCP?
Зачем нам нужен TCP? Почему нельзя просто использовать более низкий уровень — IP?
Вспомним структуру сетевого стека: физическое устройство —> канал передачи данных (Ethernet/Wi-Fi и так далее) —> Сеть (IP) —> транспортный уровень (TCP/UDP).
IP (слой 3) работает на уровне хоста, а транспортный уровень (TCP/UDP) — на уровне приложения с использованием портов. IP может доставлять пакеты целевому хосту по его IP-адресу, но как только данные достигают машины, их ещё нужно передать нужному процессу. Каждый процесс «привязывается» к порту — своему адресу в системе устройства. Если взять простую аналогию, то IP-адрес — это дом, а порт — это квартира. Вот в этих «квартирах» и проживают процессы или приложения.
Есть и ещё одна причина необходимости TCP. Если роутер (элемент инфраструктуры, который рядовые пользователи не контролируют) теряет пакеты или испытывает перегрузку, TCP на периферии (на машинах пользователей) может восстанавливать эти пакеты, не требуя участия роутеров. В итоге роутеры сохраняют свою простоту, а надёжность обеспечивается в конечных точках.
Пакеты теряются, повреждаются, дублируются и перепутываются. Так уж работает интернет. TCP же защищает разработчиков от этих проблем. Он обрабатывает повторную передачу, контрольные суммы и кучу других механизмов обеспечения надёжности. Если бы каждому разработчику приходилось реализовывать все их самому, у него бы просто не осталось времени на должное выравнивание флексбоксов — поистине ужасающая альтернативная реальность.
Ну а если серьёзно, то потрясающим делает TCP именно гарантия того, что вопреки ненадёжности сети отправленные и полученные через сокеты данные не повредятся, не дублируются и не перепутаются.
Управление потоком и перегрузкой
Если взглянуть на сетевые коммуникации обобщённо, то по факту в них происходит следующее. Машина А отправляет данные машине В. При этом машине В нужно сперва сохранить полученные данные во временном хранилище, прежде чем передавать их приложению, которое может находиться в режиме сна или быть занято. Называется это временное хранилище буфером приёма и управляется ядром ОС:
sysctl net.ipv4.tcp_rmem => net.ipv4.tcp_rmem = 4096 131072 6291456, минимум 4КБ, по умолчанию 128КБ и максимум 8 МБ.
Проблема же в том, что буфер не безграничен. Если вы передаёте большой файл (сотни МБ или даже ГБ), то можете запросто перегрузить получателя. Значит, у получателя должен быть способ сообщить отправителю, сколько ещё данных он может принять. Этот механизм называется управление потоком, и сегменты TCP включают специальное поле window, в котором указывается, сколько данных получатель в текущий момент готов принять.
Ещё одна проблема — это перегрузка самой сети, даже когда получатель располагает достаточным объёмом буфера. Ваши возможности здесь ограничиваются самым слабым каналом — некоторые каналы переносят гигабиты данных, другие лишь мегабиты. Если вы не учтёте в настройке самый медленный канал, то перегрузка неизбежна.
Занятный факт. В 1986 году пропускная способность интернета могла падать от нескольких десятков КБ/с до 40 бит/с (да, с ума сойти). Назвали это явление коллапс сети из-за перегрузки (congestion collapse). Когда пакеты терялись, и системы пытались отправить их повторно, ситуация только усугублялась — возникал порочный круг. Чтобы это исправить, в TCP встроили модели управления перегрузкой «play nice» и «back off», которые помогают интернету не удушить себя до полного отказа.
Пример кода: простой TCP-сервер
В случае низкоуровневых механизмов вроде TCP следует оперировать примерами на C. Они показывают всё как есть.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
int sockfd = -1, clientfd = -1;
void handle_sigint(int sig) {
printf("\nCtrl+C caught, shutting down...\n");
if (clientfd != -1) close(clientfd);
if (sockfd != -1) close(sockfd);
exit(0);
}
int main() {
signal(SIGINT, handle_sigint);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
// SO_REUSEADDR для принудительного привязывания к порту, даже если связанный с ним предыдущий сокет ещё закрывается (TIME_WAIT)
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(8080), .sin_addr.s_addr = INADDR_ANY };
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
printf("Listening on 8080...\n");
clientfd = accept(sockfd, NULL, NULL);
char buf[1024], out[2048];
int n;
while ((n = recv(clientfd, buf, sizeof(buf) - 1, 0)) > 0) {
buf[n] = '\0';
int m = snprintf(out, sizeof(out), "you sent: %s", buf);
printf("response %s %d\n", out, m);
send(clientfd, out, m, 0);
}
close(clientfd); close(sockfd);
}
Этот код создаст TCP-сервер, который получает от клиента сообщения и возвращает их с префиксом you sent:.
# Компиляция и запуск сервера
gcc -o server server.c && ./server
# Подключение клиента.
telnet 127.0.0.1 8080
# hi
# you sent: hi
127.0.0.1 (localhost) можно заменить удалённым IP — на работу это не повлияет.
В коде использовались следующие примитивы и функции, являющиеся частью реализации сокетов Беркли (которые были внедрены в BDS 4.2):
SOCKET: создание конечной точки (структуры в ядре).BIND: привязка к порту.LISTEN: подготовка к установке соединения и указание размера очереди ожидания (при превышении этого размера пакеты отбрасываются).ACCEPT: принятие входящего подключения (TCP-сервер).CONNECT: попытка подключения (TCP-клиент).SEND: отправка данных.RECEIVE: получение данных.CLOSE: закрытие соединения.
В примере выше мы используем взаимодействие клиент-сервер по схеме запрос-ответ. Но после отправки сообщения можно добавить следующий процесс:
send(clientfd, out, m, 0);
sleep(5);
const char *msg = "not a response, just doing my thing\n";
send(clientfd, msg, strlen(msg), 0);
Компилируем, запускаем и используем telnet:
client here
you sent: client here
client again
not a response, just doing my thing
you sent: client again
Я ввёл в терминал telnet сначала client here, а потом client again. В итоге сервер вернул лишь you sent: client here, после чего ушёл в сон. Вторая строка client again терпеливо дожидалась своей очереди в буфере приёма. Сервер ответил not a response, just doing my thing, после чего подхватил мой второй TCP-пакет и вернул уже you sent: client again.
Это полноценное дуплексное соединение. Каждая сторона отправляет то, что хочет, просто в начале одна из них слушает, а другая подключается. Дальнейшая схема взаимодействия не обязательно должна соответствовать паттерну запрос-ответ.
Иллюзорный HTTP-сервер
Теперь создадим очень простой сервер HTTP/1.1 (более поздние версии уже сложнее).
// Всё то же самое.
printf("Listening on 8080...\n");
int i = 1;
while (1) {
clientfd = accept(sockfd, NULL, NULL);
char buf[1024], out[2048];
int n;
while ((n = recv(clientfd, buf, sizeof(buf) - 1, 0)) > 0) {
buf[n] = '\0';
int body_len = snprintf(out, sizeof(out), "[%d] Yo, I am a legit web server\n", i++);
char header[256];
int header_len = snprintf(
header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n",
body_len
);
printf("header: %s\n", header);
printf("out: %s\n", out);
send(clientfd, header, header_len, 0);
send(clientfd, out, body_len, 0);
break; // Один запрос на соединение.
}
close(clientfd);
}
~ curl localhost:8080
[1] Yo, I am a legit web server
~ curl localhost:8080
[2] Yo, I am a legit web server
Здесь с помощью i отслеживается количество запросов. Мы устанавливаем TCP-соединение и возвращаем HTTP-заголовки, ожидаемые HTTP-клиентом (TCP-пиром, если быть точнее). Реальный HTTP-сервер возвращал бы подобающий HTML, CSS и JS-код, обрабатывая много разных опций и заголовков. Но внутренне это простой процесс, использующий наш надёжный и стабильный TCP.
Фактическое распределение байтов
0 <----- 32 bits ------>
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Header|Rese-| Flags | Window Size |
| Len |rved | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data (Payload) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Каждый TCP-сегмент находится внутри IP-пакета и имеет над собой заголовок. В процессе обмена данными задействовано два порта — отправки и назначения — адрес каждого из которых занимает в заголовке 16 бит. Отсюда и ограничение на общее количество портов в 64k.
Каждое соединение транспортного уровня представляет кортеж из пяти элементов (TCP/UDP, src IP, src port, dst IP, dst port).
Sequence Number и Acknowledgment Number
Надёжность TCP определяется двумя ключевыми полями: Sequence Number (номер последовательности), указывающим, какие байты последовательности несёт в себе сегмент, и Acknowledgement Number (номер подтверждения), указывающим, какие байты были получены. Sequence Number позволяет получателю видеть порядок данных, находить и переставлять выпавшие из этого порядка сегменты и обнаруживать потери. В TCP используется кумулятивное подтверждение — значение ACK равное 100 означает, что были получены байты от 0 до 99. Если байты от 100 до 120 утеряны, и поступают последующие байты, то ACK будет оставаться 100, пока не будут получены недостающие данные.
1. A --> B: Send [Seq=0-99]
2. B --> A: Send [Seq=0-49]
3. B --> A: Получает от A [0-99] --> отправляет ACK=100
4. A --> B: Получает от B [0-49] --> отправляет ACK=50
5. A --> B: Send [Seq=100-199] --- потеряны ---
6. B --> A: Send [Seq=50-99] --- потеряны ---
7. A --> B: Send [Seq=200-299]
B получает данные --> видит в них пробел (отсутствуют пакеты 100-199) --> отправляет ACK=100
8. B --> A: Send [Seq=100-149]
A получает данные --> видит в них пробел (отсутствуют пакеты 50-99) --> отправляет ACK=50
9. A --> B: Send [Seq=300-399]
B всё ещё не получил 100-199 --> отправляет ACK=100
10. B --> A: Send [Seq=150-199]
A всё ещё не получил 50-99 --> отправляет ACK=50
11. A --> B: Retransmit [Seq=100-199]
B получает данные --> и теперь у него все пакеты 0-399 --> отправляет ACK=400
12. B --> A: Retransmit [Seq=50-99]
A получает данные --> и теперь у него все пакеты 0-199 --> отправляет ACK=200
Длина заголовка показывает, сколько 4-байтовых слов в нём содержится. Она нужна, так как поле Options имеет переменную длину, а значит, и заголовок тоже.
TCP-флаги
В соединении также применяется 8 флагов, размером один бит каждый. Опишу наиболее важные из них.
SYN — используется для установки подключения. ACK — указывает на валидность номера подтверждения.
Это два основных флага, отвечающих за настройку соединения. Зачем устанавливать соединение? Чтобы обнаруживать выбившиеся из последовательности или повторяющиеся сегменты, необходимо отслеживать, какие были отправлены, а какие получены — то есть сохранять состояние соединения.
SYN и ACK задействуются при известном трёхстороннем квитировании:
A —> B:
SYN(хочу подключиться)B —> A:
SYN+ACK(получил твойSYN, тоже хочу подключиться!)A —> B:
ACK(принял, соединение установлено!)
Флаг FIN сообщает о закрытии соединения и также использует квитирование:
X —> Y:
FIN(хочу отключиться)Y —> X:
ACK(получил твойFIN, как пожелаешь!)Y —> X:
FIN(тоже хочу отключиться — иногда отправляется с предыдущимACK)X —> Y:
ACK(принял!)
Обычно это 4-х стороннее (иногда трёхстороннее) завершающее квитирование.
RST — флаг сброса. Он указывает на ошибку или принудительное отключение — в этом случае соединение тут же закрывается. Операционная система отправляет RST, если ни один процесс не прослушивает порт, или прослушивающий процесс падает. Также существует атака TCP Reset, когда промежуточный участник соединения внедряет RST для его закрытия (используется некоторыми файерволами).
Window
Это поле упоминалось при обсуждении управления потоком. Напомню, что оно указывает, сколько байтов принимающий ожидает получить после номера подтверждения.
В примере выше выполнение ss (Socket Statistics) возвращает информацию о TCP-соединении.
ss -tlpmi
// State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
// LISTEN 0 5 0.0.0.0:http-alt 0.0.0.0:* users:(("server",pid=1113,fd=3))
// skmem:(r0,rb131072,t0,tb16384,f0,w0,o0,bl0,d0) cubic cwnd:10
rb131072 (128KB) — это размер буфера приёма, а tb16384 (16КБ) — размер буфера передачи, в котором данные ожидают своей отправки по сети. В Send-Q указываются байты, которые ещё не были подтверждены удалённым хостом, а в Recv-Q — байты, которые были получены, но приложением ещё не прочитаны (например, вторая строка в сессии telnet выше, ожидающая пробуждения сервера).
Checksum
Checksum (контрольная сумма) используется для обеспечения надёжности. Все 16-битные слова в TCP-сегменте складываются вместе, и полученный результат сравнивается с контрольной суммой. Если они не совпадают, значит, какие-то биты были повреждены. В таком случае требуется повторная передача.
Заключение
Не перестаю поражаться, как всё это надёжно и непрерывно работает — сеть, интернет. Считаные десятилетия назад отправка нескольких килобайт была чуть ли не подвигом, а сегодня стриминг с разрешением 4К уже норма. Слава всем тем, кто, не жалея сил, сделал и продолжает делать это возможным!
Видео на YouTube
Комментарии (39)

ALT0105
21.11.2025 14:33TCP создавался, когда не было мобильных сетей. А радисты, когда создавали стандарты мобильных сетей, не общались с сетевиками по поводу TCP. В итоге нормальная скорость передачи данных в пакетных сетях есть только в прямой видимости от базовой станции на расстоянии не больше сотен метров, дальше - скорость падает в сотни раз. Я про это писал и предлагал способ повышения скорости мобильных сетей.

ALT0105
21.11.2025 14:33Скорость передачи пакетов в мобильном канале TCP предлагается повысить за счет сокращения числа повторных передач сбойных пакетов

poige
21.11.2025 14:33
ChatGPT 5.1 (Auto) Скрытый текст

ChatGPT 5.1 (Thinking) 
MountainGoat
21.11.2025 14:33"Обожаю" такие отзывы, безотносительно того, кто их писал. Много наукообразных букв, но по сути сводится к "тут всё неверно, что именно – не скажу".
Для меня есть фундаментальная разница между утверждениями:
У мухи не шесть ног.
У мухи не шесть ног, а восемь.
Потому что первые можно без всякого ИИ генерировать рандомной подстановкой слов, и они в большинстве своём получатся верные.
Открываешь статью, находишь grep-ом все существительные, и генерирешь фразы "{noun} описано не верно". Получаешь критику, с которой невероятно сложно дискутировать, потому что она ничего не утверждает.

poige
21.11.2025 14:33сложно дискутировать
А зачем дискутировать. Я просто попробовал и поделился тем, что текст не проходит валидацию, дальше уже дело автора — если он считает, что это сама нейронка её не проходит, это одно. А если хочет — может уже сам дальше с ней уточнять что-то.

ALT0105
21.11.2025 14:33Хорошо, второй пункт: «игнорирует реальную обработку PHY/MAC”.
Хотя в тексте:
· теория электрической связи рассматривает процессы передачи сигналов на физическом уровне OSI (модуляция, спектральная эффективность, битовая скорость) и канальном уровне (обнаружение и исправление ошибок, возникающих на физическом уровне). Конечной задачей этих уровней является обеспечение требуемой скорости передачи сигнальных блоков, состоящих из нескольких бит, с требуемой вероятностью битовой ошибки
· начиная с третьего уровня OSI и выше – это сфера IT. На четвертом уровне протокол TCP обеспечивает гарантированную доставку IP-пакетов
И так по каждому пункту

ALT0105
21.11.2025 14:33Позабавило. Нейросеть может написать все, что угодно, в том числе и раскритиковать гелиоцентрическую систему и доказать, что земля плоская. Приводятся придуманные факты – первый же пункт: «неправильно описывает архитектуру LTE/NR». В тексте вообще не описывается архитектура сети, ни LTE, ни NR. LTE упоминается только один раз, когда говорится о скорости, определенной стандартом. Аналогично всё остальное. Общая оценка – заказной бред.

poige
21.11.2025 14:33https://habr.com/ru/companies/ruvds/articles/967682/comments/#comment_29149280
Ну и:
Приводятся придуманные факты – первый же пункт: «неправильно описывает архитектуру LTE/NR»
Скрытый текст

— Можно ли сказать, что автор "неправильно описывает архитектуру LTE/NR;", поясни? 
ALT0105
21.11.2025 14:33ответил выше

poige
21.11.2025 14:33Отдельно поясню, хоть и было уже указано — самый 1-й скрин было от модели в режиме "auto", второй уже "thinking". Последующие — от "thinking", которая комментирует некоторые утверждения данные "auto".
И резюме в общем — если считаете, что ChatGPT тут «воду мутит», то и ладно. ;)

ALT0105
21.11.2025 14:33Дайте GPT эту задачу:
Заряженный плоский конденсатор расположен в вакууме вне полей тяготения и электромагнитных полей. В центре пластин есть два соосных отверстия, их ось перпендикулярна пластинам. По этой оси через конденсатор пролетает свободный электрон. Скорость электрона измеряется до конденсатора и после на одинаковом расстоянии от пластин. Изменится ли она после пролета?
Это задача по физике за 8 класс. Я уверен - не решит

poige
21.11.2025 14:33Скрытый текст


ALT0105
21.11.2025 14:33Что и требовалось доказать . Ответ не верный. Пусть освоит хотя бы уровень мышления школьника. Здесь бесполезно искать тексты, надо думать

poige
21.11.2025 14:33попытка от o3:
Скрытый текст


ALT0105
21.11.2025 14:33Лучше не стало. Эта задача и огромное множество физических и инженерных задач ИИ недоступны

ALT0105
21.11.2025 14:33Всë то же. Главная причина - для решения таких задач нужно не кучу формул писать, а понимать задачу. В правильном ответе формул нет

poige
21.11.2025 14:33однозначно. Впрочем, валидных поинтов по другим типам задач это не отменяет. ;)

poige
21.11.2025 14:33Скрытый текст

3. «Нормальная скорость только в прямой видимости и на сотнях метров»

poige
21.11.2025 14:33Скрытый текст

4. «Дальше скорость падает в сотни раз из-за несовместимости TCP и радио» «… Сводка
В цитате есть маленькое историческое зерно правды («TCP старше мобильных сетей»), но дальше автор строит ложную причинность:
он игнорирует реальную обработку PHY/MAC/RLC (Forward Error Correction, HARQ, RLC-ARQ, link adaptation), которые и сделаны, чтобы TCP работал нормально;
он подменяет peak-rate на краю идеальных условий «нормальной скоростью»;
вывод про «сотни метров» и «сотни раз падения» не соответствует ни стандартам, ни практике макросетей. …»
P. S. Вот такие вот соосные отверстия. ;)

ALT0105
21.11.2025 14:33Я в сотовой связи проработал 17 лет (в технической, не коммерческой части) и хорошо знаю и про расстояния, и про скорости, и про зоны покрытия. Описанное решение базируется на реальных измерениях, а не на рассуждениях, как должно быть.
Тогда снова отсылаю к задаче. Только большая просьба: если сами её решите, не выкладывайте решение в сеть, иначе она потеряет смысл.

ALT0105
21.11.2025 14:33Добавка про взаимодействие радистов и сетевиков. Оно до сих пор плохое - сетевики все время увеличивают размер пакета TCP, а радистам удобнее иметь меньший, но они молчат

sdy
21.11.2025 14:33данные надёжно текут в обе стороны
Обычно данные передаются, текут реки
Буквально пару дней назад была статья про паттерны сгенерированных текстов, получается генерировать можно даже перевод

Chupaka
21.11.2025 14:33Как рукопожатие в переводе стало каким-то квитированием?

Bright_Translate Автор
21.11.2025 14:33https://kartaslov.ru/значение-слова/квитирование
https://studfile.net/preview/12007105/page:51
В цикле асинхронной шины для подтверждения успешности транзакции используется двунаправленный обмен сигналами управления. Такая процедура носит название квитирования установления связи или рукопожатия (handshake).
В рассмотренном варианте процедуры ни один шаг в передаче данных не может начаться, пока не завершен предыдущий шаг. Такое квитирование известно как квитирование с полной взаимоблокировкой (fully interlocked handshake). (https://studfile.net/preview/14263363/page:4)

Chupaka
21.11.2025 14:33Квитировать — это, скорее, acknowledge. Т.е. технически, three-way handshake — это two-way acknowledge :)
З.Ы. Вторая ссылка не открывается, даёт connection reset

punhin
21.11.2025 14:33Эх, как всегда - статья закончилась на самом интересном месте! Передаём Syn-Ack, а как их увидеть в командной строке, да и вообще - как сделать всё это вручную, через программу на Си? Понимаю, что существует масса пакетов, имеющих такие функции, но всё же...

IceGerda
21.11.2025 14:33Напридумали всяких интернетов. А до этого ТВ. Сидели люди общались на завалинке, печки топили. Жизнь была реальная без этих трех букв - Web, TCP, UDP а так же без стеков, хостов и портов.. А нынче все виртуалы..

qrdl
21.11.2025 14:33одно непонятно - если TCP такой замечательный, то почему самый новый и крутой HTTP/3 как транспорт использует QUIC (у которого под капотом UDP)?

zanzack
21.11.2025 14:33Что-то ничего не увидел про фрагментацию и время жизни пакета (TTL) или это не TCP ?
Скрытый текст





VladlenBronislav
Заголовок ярче чем статья. Статья хороша, но заголовок круче.
Обратите внимание искание на congestion control от Гугла - quick . Как его продвигает Гугл, забираю эту функцию у тсп.