Открыть сервис

Berkeley sockets

Berkeley sockets — это программный интерфейс (API) для взаимодействия между процессами по сети, разработанный в Калифорнийском университете в Беркли (США) в начале 1980-х годов. Он предоставляет абстракцию «сокета» — конечной точки сетевого соединения, через которую приложения могут отправлять и получать данные, используя различные транспортные протоколы, прежде всего TCP/IP. Berkeley sockets стали стандартом де-факто для сетевого программирования в операционных системах семейства Unix и легли в основу многих современных сетевых API, включая Windows Sockets (Winsock).

История

Разработка в BSD

Первая реализация сокетов была создана в рамках проекта Berkeley Software Distribution (BSD) — версии операционной системы Unix, разрабатываемой в Калифорнийском университете в Беркли. В 1982 году в выпуске 4.1cBSD появился прототип интерфейса, а в 1983 году в 4.2BSD он был представлен как полноценный API. Основными разработчиками выступили Билл Джой (впоследствии сооснователь Sun Microsystems) и Сэмюэл Леффлер. Целью было создание универсального интерфейса, который бы абстрагировал приложения от деталей сетевых протоколов и аппаратного обеспечения.

Распространение и стандартизация

С распространением BSD-систем (FreeBSD, NetBSD, OpenBSD) и их производных (например, SunOS, Ultrix) интерфейс сокетов стал общепринятым. В 1988 году он был включён в стандарт POSIX (IEEE 1003.1), что обеспечило переносимость кода между различными Unix-подобными системами. В 1990-х годах Microsoft реализовала собственный вариант — Winsock, который, хотя и имел отличия, был основан на тех же принципах и функциях, что и Berkeley sockets.

Архитектура и основные понятия

Сокет

Сокет — это абстрактный объект, представляющий конечную точку двустороннего канала связи. В операционной системе сокет идентифицируется дескриптором (целым числом), который используется в системных вызовах для чтения, записи и управления соединением. Каждый сокет ассоциирован с конкретным сетевым протоколом и адресом (IP-адресом и портом).

Адресация

Для работы с IPv4 используется структура sockaddr_in, содержащая:

  • семейство адресов (AF_INET — для IPv4, AF_INET6 — для IPv6),
  • номер порта (16-битное целое, например, 80 для HTTP),
  • IP-адрес (32-битное целое для IPv4, 128-битное для IPv6).

Для IPv6 применяется структура sockaddr_in6, а для локальных (Unix-сокетов) — sockaddr_un.

Типы сокетов

Основные типы, определяющие характер передачи данных:

  • SOCK_STREAM — потоковый сокет, обеспечивающий надёжную двустороннюю передачу данных с установлением соединения. Обычно использует протокол TCP. Гарантирует доставку, порядок и отсутствие дублирования данных.
  • SOCK_DGRAM — дейтаграммный сокет, работающий без установления соединения. Использует протокол UDP. Данные передаются в виде отдельных сообщений (датаграмм), доставка не гарантируется.
  • SOCK_RAW — сырой сокет, позволяющий отправлять и принимать пакеты на уровне сетевых протоколов (например, ICMP, IP). Требует привилегий суперпользователя.

Протоколы

Сокет привязывается к конкретному протоколу, который задаётся при создании. Наиболее распространённые:

  • TCP (IPPROTO_TCP) — для потоковых сокетов,
  • UDP (IPPROTO_UDP) — для дейтаграммных,
  • ICMP (IPPROTO_ICMP) — для сырых сокетов.

Основные системные вызовы

Создание и закрытие

  • socket(int domain, int type, int protocol) — создаёт сокет и возвращает его дескриптор. Параметры: семейство адресов (например, AF_INET), тип сокета (SOCK_STREAM), протокол (обычно 0 для автоматического выбора).
  • close(int sockfd) — закрывает сокет и освобождает ресурсы.

Работа с адресом

  • bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) — привязывает сокет к локальному адресу (IP-адресу и порту). Обязателен для серверных сокетов.
  • listen(int sockfd, int backlog) — переводит сокет в режим ожидания входящих соединений. Параметр backlog задаёт максимальную длину очереди ожидающих соединений.
  • accept(int sockfd, struct sockaddr addr, socklen_t addrlen) — принимает входящее соединение, создавая новый сокет для обмена данными с клиентом. Блокирует выполнение до появления соединения.

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

  • connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) — устанавливает соединение с удалённым сервером. Используется для потоковых сокетов.

Передача данных

  • send(int sockfd, const void *buf, size_t len, int flags) — отправляет данные через установленное соединение (для TCP).
  • sendto(int sockfd, const void buf, size_t len, int flags, const struct sockaddr dest_addr, socklen_t addrlen) — отправляет датаграмму по указанному адресу (для UDP).
  • recv(int sockfd, void *buf, size_t len, int flags) — принимает данные из соединения (TCP).
  • recvfrom(int sockfd, void buf, size_t len, int flags, struct sockaddr src_addr, socklen_t *addrlen) — принимает датаграмму и сохраняет адрес отправителя (UDP).

Дополнительные функции

  • setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) — устанавливает опции сокета (например, SO_REUSEADDR для повторного использования порта).
  • getsockopt(int sockfd, int level, int optname, void optval, socklen_t optlen) — получает текущие значения опций.
  • getaddrinfo(const char node, const char service, const struct addrinfo *hints, struct addrinfo **res) — преобразует доменное имя и имя службы в структуры адресов, упрощая создание сокетов (рекомендуется вместо устаревшей gethostbyname).

Модели взаимодействия

Клиент-серверная модель

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

Модель без установления соединения

Используется с дейтаграммными сокетами (UDP). Сервер привязывает сокет, после чего может принимать датаграммы от любых клиентов. Клиент отправляет датаграммы на сервер без предварительного соединения. Каждая датаграмма обрабатывается независимо.

Примеры использования

Простой TCP-сервер на C (псевдокод)

``c int server_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(8080); bind(server_fd, (struct sockaddr*)&address, sizeof(address)); listen(server_fd, 3); int client_fd = accept(server_fd, NULL, NULL); char buffer[1024] = {0}; recv(client_fd, buffer, 1024, 0); send(client_fd, "Hello", 5, 0); close(client_fd); close(server_fd); ``

Простой UDP-клиент на C (псевдокод)

``c int sock = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); sendto(sock, "Hello", 5, 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); char buffer[1024] = {0}; recvfrom(sock, buffer, 1024, 0, NULL, NULL); close(sock); ``

Влияние и наследие

Стандартизация

Berkeley sockets стали основой для POSIX-стандарта сетевого программирования. Все современные Unix-подобные системы (Linux, macOS, FreeBSD, Solaris) поддерживают этот API. В Windows реализован интерфейс Winsock, который, хотя и имеет отличия, следует той же модели.

Альтернативы

В некоторых системах существуют альтернативные интерфейсы, например, Transport Layer Interface (TLI) в System V, но они не получили такого широкого распространения. В современных приложениях часто используются более высокоуровневые библиотеки (libcurl, Boost.Asio, Python sockets), которые, однако, в конечном счёте опираются на системные вызовы Berkeley sockets.

Критика

Основные недостатки интерфейса включают:

  • Сложность работы с неблокирующими операциями и асинхронным вводом-выводом (требуется использование select, poll, epoll или kqueue).
  • Низкоуровневость, требующая от программиста ручного управления буферами и обработки ошибок.
  • Отсутствие встроенной поддержки безопасности (шифрование, аутентификация) — это реализуется на более высоком уровне (например, через OpenSSL).

Интересные факты

  • Первоначально сокеты были разработаны для поддержки протоколов TCP/IP, но впоследствии были адаптированы для других протоколов (например, Unix-сокеты для локального межпроцессного взаимодействия).
  • Термин «сокет» (socket) был заимствован из электрической инженерии, где он обозначает разъём для подключения.
  • В 1980-х годах интерфейс сокетов был критически важен для распространения стека TCP/IP в академической среде и последующего превращения его в стандарт Интернета.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →