Практически любое современное приложение взаимодействует с сетью. Веб-браузеры, мессенджеры, онлайн-игры, базы данных, облачные сервисы - все они передают данные между компьютерами. Однако программисты редко работают напрямую с сетевыми пакетами, IP-заголовками или механизмами доставки данных. Вместо этого операционная система предоставляет удобный программный интерфейс - интерфейс сокетов (Socket API). Именно сокеты стали де-факто стандартом взаимодействия приложений с транспортным уровнем сетевого стека.

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

Если провести аналогию с файловой системой, то:

  • файл - интерфейс для работы с диском;
  • сокет - интерфейс для работы с сетью.

Для программиста сокет выглядит как специальный дескриптор (идентификатор ресурса), через который можно выполнять операции чтения и записи данных. Сам сокет не является протоколом передачи данных. Это интерфейс, предоставляемый операционной системой для доступа к сетевым возможностям.


Немного истории

В начале развития компьютерных сетей каждая операционная система предлагала собственные способы работы с сетью. Это делало перенос программ между платформами весьма сложным.

Ситуация изменилась в начале 1980-х годов, когда в Калифорнийском университете в Беркли была разработана реализация TCP/IP для BSD UNIX. Вместе с ней появился программный интерфейс, получивший название Berkeley Sockets.

Именно этот интерфейс оказался настолько удобным, что постепенно был принят практически всеми операционными системами:

  • Linux;
  • BSD;
  • macOS;
  • Windows (Winsock является адаптацией Berkeley Sockets).

Сегодня практически любой сетевой язык программирования - будь то C, C++, Go, Rust или C# - внутри использует именно концепцию сокетов.


Почему нужен интерфейс сокетов?

Передача данных по сети - задача достаточно сложная. Необходимо:

  • разбивать данные на сегменты;
  • формировать IP-пакеты;
  • вычислять контрольные суммы;
  • отслеживать потерянные пакеты;
  • выполнять повторную передачу;
  • управлять перегрузкой сети;
  • устанавливать соединение;
  • закрывать соединение.

Все эти механизмы реализуются транспортным уровнем и нижележащими уровнями сетевого стека.

Программисту вовсе не нужно самостоятельно реализовывать TCP или UDP. Вместо этого он работает с простым набором функций:

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

Все остальные детали скрыты внутри операционной системы.

Именно поэтому говорят, что Socket API абстрагирует транспортный уровень, позволяя разработчику работать с сетью на более высоком уровне.

Важно понимать различие между протоколом и интерфейсом.

Протокол определяет:

  • формат сообщений;
  • правила передачи;
  • порядок установления соединения;
  • обработку ошибок.

Интерфейс определяет:

  • какие функции вызывает программист;
  • какие параметры они принимают;
  • какие данные возвращают.

Berkeley Sockets

Практически все реализации Socket API основаны на классическом интерфейсе Berkeley Sockets.

Рассмотрим основные функции.

- socket()

Создает новый сокет.

На этом этапе операционная система выделяет необходимые внутренние структуры.

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

- bind()

Связывает сокет с определенным IP-адресом и номером порта. Обычно используется сервером.

После выполнения bind() операционная система знает, на каком порту необходимо ожидать входящие подключения.

- listen()

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

- accept()

Принимает новое входящее соединение. 

Очень важно понимать, что после вызова accept() появляется новый сокет, предназначенный для общения именно с данным клиентом.

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

- connect()

Используется клиентом.

Функция инициирует установление соединения с удаленным сервером. Для TCP именно здесь начинается процедура трехстороннего рукопожатия (Three-Way Handshake).

- send()

Передает данные удаленной стороне.

Для TCP данные помещаются в поток передачи.

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

- receive()

Получает данные из сокета.

Функция считывает данные, которые уже были получены транспортным уровнем и помещены в буфер сокета.

- close()

Закрывает соединение.

После вызова этой функции освобождаются системные ресурсы, связанные с сокетом. Для TCP также выполняется корректное завершение соединения.


Source: Orkhan Alishov's notes