Практически любое современное приложение взаимодействует с сетью. Веб-браузеры, мессенджеры, онлайн-игры, базы данных, облачные сервисы - все они передают данные между компьютерами. Однако программисты редко работают напрямую с сетевыми пакетами, IP-заголовками или механизмами доставки данных. Вместо этого операционная система предоставляет удобный программный интерфейс - интерфейс сокетов (Socket API). Именно сокеты стали де-факто стандартом взаимодействия приложений с транспортным уровнем сетевого стека.
Сокет (Socket) - это программная конечная точка сетевого соединения, через которую приложение может отправлять и получать данные.
Если провести аналогию с файловой системой, то:
Для программиста сокет выглядит как специальный дескриптор (идентификатор ресурса), через который можно выполнять операции чтения и записи данных. Сам сокет не является протоколом передачи данных. Это интерфейс, предоставляемый операционной системой для доступа к сетевым возможностям.
Немного истории
В начале развития компьютерных сетей каждая операционная система предлагала собственные способы работы с сетью. Это делало перенос программ между платформами весьма сложным.
Ситуация изменилась в начале 1980-х годов, когда в Калифорнийском университете в Беркли была разработана реализация TCP/IP для BSD UNIX. Вместе с ней появился программный интерфейс, получивший название Berkeley Sockets.
Именно этот интерфейс оказался настолько удобным, что постепенно был принят практически всеми операционными системами:
Сегодня практически любой сетевой язык программирования - будь то C, C++, Go, Rust или C# - внутри использует именно концепцию сокетов.
Почему нужен интерфейс сокетов?
Передача данных по сети - задача достаточно сложная. Необходимо:
Все эти механизмы реализуются транспортным уровнем и нижележащими уровнями сетевого стека.
Программисту вовсе не нужно самостоятельно реализовывать 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