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

В типичной архитектуре клиент-сервер приложения взаимодействуют с помощью сокетов следующим образом:

1. Каждое приложение создает сокет. Сокет - это устройство, обеспечивающее связь, и оба приложения требуют его.

2. Сервер привязывает свой сокет к адресу (имени), чтобы клиент мог его найти.

Сокет создается с помощью системного вызова socket (), который возвращает дескриптор файла, используемый для ссылки на сокет в последующих системных вызовах.

fd = socket (домен, тип, протокол)

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

Коммуникационные домены

Домен связи - это метод определения сокета и диапазона связи. Современные операционные системы поддерживают как минимум следующие домены:

1. UNIX (AF_UNIX)
- Связь осуществляется: внутри ядра
- Связь с ч / б приложениями: на одном хосте
- Формат адреса: путь
- Структура адреса: sockaddr_un

2. IPv4 (AF_INET)
- Связь осуществляется: через IPc4
- Связь с приложениями ч / б: на хостах, подключенных через IPv4
- Формат адреса: 32-битный адрес IPv4 + 16-битный порт
- Структура адреса: sockaddr_in

3. IPv6 (AF_INET6)
- Связь осуществляется: внутри ядра
- Связь с приложениями ч / б: на хостах, подключенных через IPv6
- Формат адреса: 128-битный адрес IPv6 + 16-битный порт
- Структура адреса: sockaddr_in6

Типы розеток

Каждая реализация Socket предоставляет как минимум два типа сокетов.

1. Stream Socket:
- Надежная доставка: Да
- Сохранение границ сообщения: Нет
- Ориентированное на соединение: Да

Потоковые сокеты (SOCK_STREAM) обеспечивают надежный двунаправленный канал связи с потоком байтов.

2. Сокет дейтаграммы:
- Надежная доставка: Нет
- Сохранение границ сообщения: Да
- Ориентированное на соединение: Нет

Сокеты дейтаграмм (SOCK_DGRAM) позволяют обмениваться данными в форме сообщений, называемых дейтаграммами, которые ненадежны, но сохраняют границы сообщения.

Системные вызовы Socket

Ключевые системные вызовы сокета:
- socket () создает новый сокет.
- bind () связывает сокет с адресом. Сервер использует этот вызов для привязки своего сокета к адресу.
- listen () позволяет потоковому сокету принимать входящие соединения от других сокетов.
- accept () принимает соединение от однорангового приложения в прослушивающем потоке socket.
- connect () установил соединение с другим сокетом.

Ввод-вывод сокета может быть выполнен с помощью обычных системных вызовов read () и write () или с помощью ряда системных вызовов, специфичных для сокетов, таких как send (), recv (), sendto () и recvfrom ().

Создание сокета

Системный вызов socket () создает новый сокет
- - - - - - - - - - - - - - - - -
#include ‹sys / socket.h›
/ *
Прототип системного вызова socket ()
возвращает дескриптор файла в случае успеха или -1 в случае ошибки

домен - это домен сокета
тип - тип сокета
протокол обычно 0
* /

сокет int (домен int, тип int, протокол int)

— — — — — — — — — — — — — — — — — —

Связывание сокета

Системный вызов bind () связывает сокет с адресом.
- - - - - - - - - - - - - - - - -
#include ‹sys / socket.h ›
/ *
Прототип системного вызова bind ()
возвращает 0 в случае успеха или -1 в случае ошибки
sockfd - дескриптор файла, полученный в результате вызова socket ()
addr - указатель на структуру, определяющую адрес, к которому он должен быть привязан
addrlen указывает длину адресной структуры
* /

int bind (int sockfd, const struct sockadd * addr , socklen_t addrlen);

- - - - - - - - - - - - - - - - - - -

Структура адреса сокета:

— — — — — — — — — — — — — — — — —

struct sockaddr {
sa_family_t sa_family; // Семейство адресов (AF_ константа)
char sa_data [14]; // адрес сокета (размер зависит от домена сокета)
}

- - - - - - - - - - - - - - - - -

Эта структура служит шаблоном для всех доменных структур адресов.

Потоковые сокеты

Работа потокового сокета объясняется ниже:

1. Системный вызов socket () создает сокет. У обоих приложений должен быть сокет.
2. Одно из приложений вызывает bind (), чтобы привязать сокет к адресу, а затем вызывает listen (), чтобы уведомить ядро о своем желании принимать входящие соединения.
3. Другое приложение устанавливает соединение, вызывая connect (), указывая адрес сокета, к которому должно быть выполнено соединение.
4. Приложение, которое вызвало listen (), затем принимает соединение с помощью accept ().
5. После установления соединения данные могут передаваться в обоих направлениях между приложениями, пока одно из них не закроет соединение. используя close (). Связь осуществляется с помощью системных вызовов read () и write () или через специальные вызовы сокета, такие как send () и recv ().

Активные и пассивные сокеты

Потоковые сокеты бывают активными и пассивными:

-Активный сокет
По умолчанию активен сокет, созданный с помощью socket (). Активный сокет можно использовать в вызове connect () для установления соединения с пассивным сокетом. Это называется активным открытием. Обычно это клиент.

-Пассивный сокет
Пассивный сокет (также называемый прослушивающим сокетом) - это тот, который был помечен как разрешающий входящее соединение путем вызова listen (). Принятие входящего соединения называется пассивным открытием. Обычно это сервер.

Прослушивание входящих соединений

Системный вызов listen () помечает сокет потока, на который ссылается файловый дескриптор sockfd, как пассивный.

— — — — — — — — — — — — — — — — —

#include ‹sys / socket.h›
/ *
Прототип системного вызова listen ()
возвращает 0 в случае успеха и -1 в случае ошибки.
sockfd - это сокет Задержка файлового дескриптора - это ограничение на количество разрешенных ожидающих запросов.
* /
int listen (int sockfd, int backlog)

— — — — — — — — — — — — — — — — —

Мы не можем применить прослушивание к подключенному сокету. Клиент может вызвать connect () до того, как сервер вызовет accept (). Это могло произойти, если сервер занят обработкой других клиентов. Это приводит к незавершенному соединению. Ядро должно записывать информацию о каждом ожидающем запросе на соединение, чтобы можно было обработать последующий accept (). Аргумент backlog позволяет нам ограничить количество таких ожидающих соединений.

Принятие подключения

Системный вызов accept () принимает входящее соединение на сокете прослушивающего потока.

— — — — — — — — — — — — — — — — —

#include ‹sys / socket.h›
/ *
Прототип системного вызова accept ().
возвращает дескриптор файла в случае успеха и -1 в случае ошибки.
sockfd is дескриптор файла сокета.
addr и addrlen возвращают адрес однорангового сокета.
* /
int accept (int sockfd, struct sockaddr * addr, socklen_t * addrlen);

— — — — — — — — — — — — — — — — —

Accept () создает новый сокет, и именно этот новый сокет подключен к одноранговому сокету, который выполнил connect (). Вызов возвращает файловый дескриптор для подключенного сокета.
Слушающий сокет (sockfd) остается открытым и может использоваться для приема дальнейших подключений.
Если нас не интересует адрес однорангового сокета , тогда addr и addrlen должны быть NULL и 0 соответственно.

Подключение к одноранговому сокету

Системный вызов connect соединяет активный сокет, на который указывает файловый дескриптор sockfd, с прослушивающим сокетом, адрес которого указан в addr и addrlen.
- - - - - - - - - - - - - - -
#include ‹sys / socket.h›
/ *
Прототип системного вызова connect ().
возвращает 0 в случае успеха и -1 в случае ошибки.
sockfd - это файловый дескриптор активного сокета.
addr и addrlen указывают адрес слушающего сокета.
* /
int connect (int sockfd, struct sockaddr * addr, socklen_t * addrlen);

— — — — — — — — — — — — — — — —

Сокеты потоков ввода-вывода

- ›Для выполнения ввода-вывода мы используем системные вызовы read () и write () или системные вызовы send () и recv ()
-› Сокет может быть закрыт с помощью системного вызова close () или вследствие приложение завершается. После этого, когда одноранговое приложение пытается прочитать с другого конца соединения, оно получает EOF (конец файла). Если одноранговое приложение пытается записать в свой сокет, оно получает сигнал SIGPIPE, и системный вызов завершается с ошибкой EPIPE.

Прерывание соединения

Обычный способ разорвать соединение с потоковым сокетом - вызвать close (). Если несколько файловых дескрипторов относятся к одному и тому же сокету, то соединение разрывается, когда все дескрипторы закрываются.