Сокеты и Веб-сокеты: От системных вызовов 80-х до Real-time веба
Сетевые сокеты (Berkeley Sockets).
С точки зрения операционной системы, сокет - это дескриптор файла. В Unix-подобных системах "всё есть файл", и сокет не исключение. Это абстракция, которая позволяет программе читать и записывать данные в сеть так же просто, как в текстовый документ на диске.
Техническая формула: Socket = IP Address + Port + Protocol (TCP/UDP)
Дескриптор файла - это маленькое целое число, которое операционная система выдаёт процессу, когда тот открывает файл, сокет, канал, pipe, устройство или любой другой ресурс ввода‑вывода. Это идентификатор, через который программа взаимодействует с ресурсом.
История: Эпоха BSD
История Berkeley Sockets API - это фактически история того, как интернет стал интернетом. До 1983 года мир сетевых технологий напоминал Вавилонскую башню: каждый производитель железа (IBM, DEC, Xerox) имел свои протоколы, которые не умели "разговаривать" друг с другом.
В начале 80-х программирование под сеть было кошмаром. Если вы писали софт для мейнфрейма IBM, вы использовали одни системные вызовы; для машин DEC - другие. Не существовало единой абстракции "соединения".
Разработчики из Computer Systems Research Group (CSRG) в Университете Беркли, работая над релизом 4.2BSD, поставили цель: сделать работу с сетью такой же простой, как работу с файлами в Unix.
"Всё есть файл"
Гениальность Berkeley Sockets заключалась в адаптации концепции Unix "Everything is a file".
- Чтобы прочитать данные из файла, вы его открываете, читаете и закрываете.
- Билл Джой и его команда предложили делать то же самое с сетью.
Они ввели понятие дескриптора сокета. Сокет - это "конечная точка" (IP-адрес + Порт). Программисту стало неважно, как именно пакеты летят по проводам; ему достаточно было создать сокет и писать в него данные.
Популярность Berkeley Sockets была обусловлена двумя факторами:
- Открытость: Код BSD был доступен для изучения и копирования.
- Финансирование DARPA: Агентство продвигало TCP/IP как основной протокол для своей сети (предшественника интернета), и реализация Беркли была лучшей на рынке.
Как это работает жизненный цикл сокета(Lifecycle)?
Жизненный цикл сокета - это последовательность системных вызовов, через которые проходит любое сетевое соединение. Каждый шаг меняет состояние сокета в ядре и определяет, что с ним можно делать дальше. Ниже - подробное, но компактное объяснение, ориентированное на разработчика, который хочет понимать, что реально происходит под капотом.
Чтобы понять, как работает жизненный цикл сокета, проще всего представить его как процесс установки телефонной связи в офисе. Есть "телефонный аппарат" (сам сокет), "номер" (IP и порт) и "оператор" (ядро ОС).

1. socket() - Покупка телефона
Процесс начинается с системного вызова socket(). На этом этапе вы просто сообщаете операционной системе: "Мне нужно устройство для связи".
- Что происходит: ОС выделяет ресурс и возвращает дескриптор (целое число).
- Параметры: Вы выбираете "тип" связи. Обычно это AF_INET (IPv4) и SOCK_STREAM (TCP, для надежности) или SOCK_DGRAM (UDP, для скорости).
2. bind() - Присвоение номера
У вас есть телефон, но у него нет номера. Вызов bind() привязывает сокет к конкретному адресу сетевой карты и порту.
- Для сервера: Это обязательно. Сервер должен "сидеть" на известном порту (например, 80 для HTTP), чтобы клиенты знали, куда стучаться.
- Для клиента: Обычно не вызывается вручную; ОС сама выделяет свободный случайный порт при подключении.
3. listen() - Перевод в режим ожидания (Только сервер)
Этот вызов превращает обычный сокет в пассивный. Сервер говорит системе: "Я готов принимать звонки".
- Очередь (backlog): В параметрах указывается размер очереди. Если 10 клиентов постучатся одновременно, а сервер занят, listen определит, сколько из них подождут, а кому сразу придет отказ.
- Что происходит, если очередь заполнена? Пришли 10 клиентов, они сидят в очереди в ядре ОС. Пришёл 11, ОС смотрит "мест нет". ОС либо просто игнорирует пакет (клиент отвалится по таймауту), либо отправляет ему
ECONNREFUSED(отказ в соединении).
4. connect() vs accept() - Установка связи
Здесь пути клиента и сервера расходятся:
connect()(Клиент): Клиент инициирует "трехэтапное рукопожатие" (TCP Three-way Handshake). Он отправляет запрос серверу.accept()(Сервер): Это блокирующий вызов. Сервер "засыпает" на этой строчке кода, пока не придет клиент. Как только соединение установлено, accept "просыпается" и создает новый отдельный сокет специально для этого клиента.- Важно: Основной сокет продолжает слушать других, а новый - используется для общения с конкретным подключившимся пользователем.
5. send() / recv() - Разговор
Когда соединение установлено, начинается обмен данными.
- Байты, а не объекты: Сокеты ничего не знают о JSON, картинках или тексте. Они передают только сырые байты.
- Потоковый режим: В TCP данные могут прийти не целиком, а кусками. Разработчику нужно проверять, сколько байт реально получено, и "склеивать" их.
6, close() - Повесить трубку
Когда данные переданы, одна из сторон (или обе) вызывает close(). Это высвобождает дескриптор в ОС и закрывает порт.
WebSockets: Живое общение в браузере
Протокол HTTP (до версии 1.1 включительно) был "молчаливым". Клиент спросил - сервер ответил - соединение закрылось. Чтобы сделать чат, браузеру приходилось каждые 2 секунды отправлять пустые запросы (Polling). Это создавало огромную нагрузку на сервер и дикие задержки.
Протокол WebSocket (RFC 6455)
В 2011 году появился WebSocket. Его главная фишка - Full-Duplex (полный дуплекс). Это значит, что и клиент, и сервер могут одновременно слать данные друг другу по одному открытому каналу.
Почему HTTP не справлялся?
Чтобы понять ценность WebSocket, нужно осознать масштаб проблемы Polling (опроса).
Представьте чат на 1000 человек. При обычном опросе (Short Polling) сервер получает 500 запросов в секунду, даже если никто ничего не пишет. Каждый такой запрос - это:
- Установление TCP-соединения (3-way handshake).
- Огромные HTTP-заголовки (Cookies, User-Agent), которые весят больше, чем само сообщение "Привет".
- Закрытие соединения.
Long Polling (длинные опросы) немного спасали ситуацию: сервер держал запрос открытым, пока не появятся данные. Но это все равно был "костыль", съедающий ресурсы сервера.
Внутри канала: Что такое Фреймы (Frames)?
Когда соединение установлено, данные больше не передаются в виде текста с заголовками. Они упаковываются в бинарные фреймы.
Фрейм - это очень компактный конверт. В нем есть:
- FIN бит: Указывает, является ли этот кусок данных финальным или за ним последуют еще.
- Opcode: Тип данных (0x1 - текст, 0x2 - бинарные данные, 0x8 - закрытие соединения, 0x9 - пинг).
- Payload length: Размер данных. Для маленьких сообщений заголовок фрейма весит всего 2-10 байт. Сравни это с 500+ байтами заголовков HTTP.
Ключевые отличия для IT-специалиста
Сетевой сокет (L4): Это программный интерфейс (API) операционной системы. Когда ты открываешь сокет, ты говоришь ОС: "Выдели мне порт и отправляй все байты с этого IP-адреса моему приложению".
WebSocket (L7): Это протокол прикладного уровня. Он добавляет к "сырому" сокету правила: как поздороваться (Handshake), как зашифровать данные (Masking) и как делить поток байтов на понятные сообщения (Frames).
| Характеристика | Сетевые сокеты (TCP/UDP) | WebSockets |
|---|---|---|
| Уровень OSI | Транспортный (L4) | Прикладной (L7), работает поверх TCP |
| Среда выполнения | ОС, системные вызовы, backend | Браузеры, веб‑серверы |
| Формат данных | Сырые байты. Нет понятия "сообщение", только поток. | Фреймы. Есть четкие границы сообщений (текст/бинарные). |
| API‑сложность | Низкоуровневая (нужно самому склеивать пакеты). | Высокоуровневая (события: onmessage, onerror). |
| Транспорт | TCP или UDP | Только TCP (как база для надежности) |
| Безопасность | Прямой доступ к портам (опасно для браузера). | Работает через HTTP Handshake, поддерживает шифрование (WSS). |
| Адресация | IP-адрес и порт (напр. 192.168.1.1:8080) | URL-схема (напр. wss://example.com/chat) |
| Проход через Proxy | Часто блокируются корпоративными фаерволами. | Маскируются под HTTP, легко проходят через прокси. |
Когда и что выбирать?
| Ситуация | Что использовать? | Почему? |
|---|---|---|
| Браузерный чат / Уведомления | WebSockets | Единственный стандартный способ держать живое соединение в JS. |
| Мобильная игра (Unity/C++) | TCP/UDP сокеты | Минимальные задержки, нет лишнего оверхеда протокола WebSocket. |
| Система мониторинга (Веб-панель) | WebSockets | Удобство интеграции с React/Vue и простота API. |
| Передача потокового видео (Real-time) | WebRTC (UDP-based) | WebSockets могут быть медленными из-за TCP-контроля доставки. |