Главная » 2017 » Ноябрь » 18 » man 7 unix
21:46
man 7 unix

SEO sprint - Всё для максимальной раскрутки!





ИМЯ


unix - сокеты для локального межпроцессного взаимодействия



ОБЗОР


#include <sys/socket.h>
#include <sys/un.h>

unix_socket = socket(AF_UNIX, type, 0);
error = socketpair(AF_UNIX, type, 0, int *sv);



ОПИСАНИЕ


Семейство сокетов AF_UNIX (также известное, как AF_LOCAL) используется для
эффективного взаимодействия между процессами на одной машине. Доменные сокеты UNIX
могут быть как безымянными, так и иметь имя файла в файловой системе
(типизированный сокет). В Linux также поддерживается абстрактное пространство
имён, которое не зависит от файловой системы.

Допустимые типы сокета для домена UNIX: потоковый сокет SOCK_STREAM, датаграмный
сокет SOCK_DGRAM, сохраняющий границы сообщений (в большинстве реализаций UNIX,
доменные датаграмные сокеты UNIX всегда надёжны и не меняют порядок датаграмм); и
(начиная с Linux 2.6.4) ориентированный на соединение задающий последовательность
пакетам сокет SOCK_SEQPACKET, сохраняющий границы сообщений и доставляющий
сообщения в том же порядке, в каком они были отправлены.

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

Формат адреса
Адрес доменного сокета UNIX представляет собой следующую структуру:

struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* имя пути */
};

Поле sun_family всегда содержит AF_UNIX. В Linux размер sun_path равен 108 байтам;
также смотрите ЗАМЕЧАНИЯ ниже.

В различных системных вызовах (например, bind(2), connect(2) и sendto(2)) в
качестве входных данных используется параметр sockaddr_un. Другие системные вызовы
(например, getsockname(2), getpeername(2), recvfrom(2) и accept(2)) возвращают
результат в параметре этого типа.

В sockaddr_un структуре различают три типа адресов:

* с именем пути: доменный сокет UNIX может быть привязан к имени пути (с
завершающимся null) в файловой системе с помощью bind(2). При возврате адреса
имени пути сокета (одним и системных вызовов, упомянутых выше), его длина равна

offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1

и sun_path содержит путь, оканчивающийся null (в Linux, указанное выше
выражение offsetof() равно sizeof(sa_family_t), но в некоторых реализациях
включаются другие поля перед sun_path, поэтому выражение offsetof() описывает
размер адресной структуры более переносимым способом).

пространстве имён определяется дополнительными байтами в sun_path, количество
которых определяется длиной указанной структуры адреса. Байты null в имени не
имеют специального значения. Имя не связано с именем пути в файловой системе.
При возврате адреса абстрактного сокета возвращаемое значение addrlen больше
чем sizeof(sa_family_t) (т.е. больше 2), а имя сокета содержится в первых
(addrlen - sizeof(sa_family_t)) байтах sun_path.

Путевые сокеты
При привязке сокета к пути для максимальной переносимости и простоте кодирования
нужно учесть несколько правил:

* Имя пути в sun_path должно завершаться null.

* Длина имени пути, включая завершающий байт null, не должна превышать размер
sun_path.

* Аргумент addrlen, описывающий включаемую структуру sockaddr_un, должен
содержать значение, как минимум:

offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path)+1

или, проще говоря, для addrlen можно использовать sizeof(struct sockaddr_un).

Есть несколько реализаций по работе с адресами доменных сокетов UNIX, которые не
следуют данным правилам. Например, в некоторых реализациях (но не во всех)
добавляется конечный null, если если его нет в sun_path.

При написании переносимых приложений учтите, что в некоторых реализациях размер
sun_pathравен 92 байтам.

Различные системные вызовы (например, accept(2), recvfrom(2), getsockname(2),
getpeername(2)) возвращают адресные структуры сокета. В случае с доменными
сокетами UNIX аргумент значение-результат addrlen, передаваемый вызову, должен
быть инициализирован как описано выше. При возврате в аргументе содержится
реальный размер адресной структуры. Вызывающий должен проверить полученное
значение этого аргумента: если оно превышает значение до вызова, то не
гарантируется наличие конечного null в sun_path (смотрите ДЕФЕКТЫ).

Пути к сокетам и права
В реализации Linux учитываются права на каталоги, в которых располагаются сокеты.
Создание нового сокета завершится ошибкой, если процесс не имеет права писать или
искать (выполнять) в каталог, в котором создаётся сокет.

В Linux для подключения к объекту потокового сокета требуются права на запись в
этот сокет; схожим образом, для отправки дейтаграммы в дейтаграммный сокет
требуются права на запись в этот сокет. В POSIX ничего не сказано о влиянии прав
файла сокета и в некоторых системах (например, в старых BSD) права на сокет
игнорируются. Переносимые программы не должны полагаться на это свойство для
обеспечения безопасности.

При создании нового сокета владелец и группа файла сокета назначаются согласно
обычных правил. К файлу сокета разрешается любой доступ кроме выключенного
процессом с помощью umask(2).

Владелец, группа и права доступа пути сокета можно изменять (с помощью chown(2) и
chmod(2)).

Пространство имён абстрактных сокетов является непереносимым расширением Linux.

Параметры сокета
В силу исторических причин эти параметры сокетов относятся к типу SOL_SOCKET, даже
если они относятся к AF_UNIX. Они могут быть установлены с помощью setsockopt(2) и
прочитаны с помощью getsockopt(2); тип SOL_SOCKET указывается в качестве семейства
сокета.

SO_PASSCRED
Разрешает приём информационных данных (credentials) посылающего процесса во
вспомогательном сообщении. Если при включении этого параметра сокет пока
ещё не соединён, то в абстрактном пространстве имён будет автоматически
создано уникальное имя. Ожидается целочисленный логический флаг.

Свойство автоматической привязки
Если в вызов bind(2) передано значение addrlen равное sizeof(sa_family_t), или для
сокета, который не привязан к адресу явно, был указан параметр сокета SO_PASSCRED,
то сокет автоматически привязывается к абстрактному адресу. Адрес состоит из байта
null и 5 байтов символов из набора [0-9a-f]. Таким образом, максимальное
количество автоматически привязываемых адресов равно 2^20 (в Linux 2.1.15, когда
была добавлена автоматическая привязка, использовалось 8 байт, и, таким образом,
ограничение было 2^32 адресов. В Linux 2.3.15 количество байт сократили до 5).

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

Доменные сокеты UNIX не поддерживают передачу внеполосных данных (флаг MSG_OOB у
send(2) и recv(2)).

Флаг MSG_MORE у send(2) не поддерживается доменными сокетами UNIX.

До Linux 3.4 использование MSG_TRUNC в аргументе flags у recv(2) не поддерживалось
доменными сокетами UNIX.

Параметр сокета SO_SNDBUF учитывается в доменных сокетах UNIX, а параметр
SO_RCVBUF — нет. Для датаграмных сокетов значение SO_SNDBUF считается максимальным
размером для исходящих датаграмм. Это ограничение, вычисляемое как удвоенное
значение (см. socket(7)) параметра, содержит меньше 32 байт накладных расходов.

Вспомогательные сообщения
Вспомогательные данные отправляются и принимаются с помощью sendmsg(2) и
recvmsg(2). В силу исторических причин перечисленные типы вспомогательных
сообщений относятся к типу SOL_SOCKET, даже если они относятся к AF_UNIX. Для
того, чтобы отправить их, установите значение поля cmsg_level структуры cmsghdr
равным SOL_SOCKET, а в значении поля cmsg_type укажите его тип. Дополнительная
информация приведена в cmsg(3).

SCM_RIGHTS
Передать или принять набор открытых файловых дескрипторов из другого
процесса. Часть с данными содержит целочисленный массив файловых
дескрипторов. Переданные файловые дескрипторы действуют так, как если бы
они были созданы dup(2).

SCM_CREDENTIALS
Передать или принять информацию о UNIX. Может быть использовано для
аутентификации. Информация передаётся в виде структуры struct ucred
Начиная с glibc 2.8, чтобы получить определение данной структуры должен
быть определён макрос тестирования свойств _GNU_SOURCE (до включения
каких-либо заголовочных файлов).

Информация (credentials), указываемая отправителем, проверяется ядром.
Процесс с идентификатором эффективного пользователя 0 может указывать
значения, отличные от его собственных. Отправитель должен указать
идентификатор своего процесса (если только он не имеет мандата
CAP_SYS_ADMIN), свой реальный идентификатор пользователя, эффективный
идентификатор или сохранённый set-user-ID (если только он не имеет
CAP_SETUID) и реальный идентификатор своей группы, эффективный
идентификатор группы или сохранённый set-group-ID (если только он не имеет
CAP_SETGID). Для получения сообщения со структурой struct ucred для сокета
нужно включить параметр SO_PASSCRED.

Вызовы ioctl
Следующие вызовы ioctl(2) возвращают информацию в аргументе value. Корректный
синтаксис:

int value;
error = ioctl(unix_socket, ioctl_type, &value);

Значением ioctl_type может быть:

SIOCINQ
Для сокета SOCK_STREAM функция возвращает количество непрочитанных данных в
очереди в приёмном буфере. Сокет не должен быть в состоянии LISTEN, иначе
возвращается ошибка (EINVAL). Значение SIOCINQ определено в
<linux/sockios.h>. В качестве альтернативы вы можете использовать синоним
FIONREAD, определённый в <sys/ioctl.h>. Для сокета SOCK_DGRAM возвращаемое
значение совпадает с датаграммным сокетом домена Интернета; смотрите
udp(7).



ОШИБКИ


EADDRINUSE
Заданный локальный адрес уже используется, или сокетный объект файловой
системы уже существует.

ECONNREFUSED
Удалённый адрес, указанный connect(2) не является слушающим сокетом. Эта
ошибка также может возникнуть, если путь назначения не является сокетом.

ECONNRESET
Удалённый сокет был неожиданно закрыт.

EFAULT Некорректный адрес пользовательской памяти.

EINVAL Передан неправильный аргумент. Основная причина — не задано значение
AF_UNIX в поле sun_type передаваемых адресов или сокет находится в
некорректном состоянии для производимой операции.

EISCONN
Вызов connect(2) запущен для уже соединённого сокета, или адрес назначения
указывает на соединённый сокет.

ENOENT Путь, указанный в удалённом адресе для connect(2), не существует.

EPERM Отправитель указал неправильную информацию (credentials) в структуре struct
ucred.

EPIPE Удалённый сокет был закрыт в потоковом сокете. Если разрешено, также будет
послан сигнал SIGPIPE. Этого можно избежать, передав флаг MSG_NOSIGNAL при
вызове send(2) или sendmsg(2).

EPROTONOSUPPORT
Указанный протокол не является AF_UNIX.

EPROTOTYPE
Удалённый сокет не совпадает с типом локального сокета (SOCK_DGRAM против
SOCK_STREAM).

ESOCKTNOSUPPORT
Неизвестный тип сокета.

ETOOMANYREFS
Эта ошибка может возникнуть в sendmsg(2) при передаче через доменный сокет
UNIX в качестве вспомогательных данных файлового дескриптора (смотрите
описание SCM_RIGHTS выше). Это происходит, если количество файловых
дескрипторов «в полёте» превышает ограничитель ресурса RLIMIT_NOFILE и
вызывающий не имеет мандата CAP_SYS_RESOURCE. Файловым дескриптором в
полёте считается посланный с помощью sendmsg(2), но ещё не принятый
процессом-получателем с помощью recvmsg(2).

Данная ошибка выявляется начиная с Linux 4.5 (и в некоторых старых версиях,
в которые перенесено исправление). В ранних версиях ядра было возможно
получить неограниченное количество файловых дескрипторов в полёте, посылая
каждый файловый дескриптор с помощью sendmsg(2) и затем закрывая файловый
дескриптор, и таким образом он не учитывался в ограничителе ресурса
RLIMIT_NOFILE.

При создании сокетного объекта на уровне сокетов или файловой системы могут
генерироваться другие ошибки. За дополнительной информацией обращайтесь к
соответствующей справочной странице.



ВЕРСИИ


SCM_CREDENTIALS и абстрактное пространство имён появились в Linux 2.2 и не должны
использоваться в переносимых программах. Некоторые клоны BSD также поддерживают
передачу дополнительной информации (credential), но методы реализации передачи
могут серьезно отличаться на разных системах.



ЗАМЕЧАНИЯ


Привязка сокета к имени файла создаёт сокет в файловой системе, который должен
быть удалён создателем, когда необходимость в нём отпадёт (с помощью unlink(2)).
Обычная система ссылок UNIX также подходит для работы с сокетами; сокет может быть
удалён в любое время, а реальное удаление из файловой системы будет произведено
при закрытии последней на него ссылки.

Для передачи файловых дескрипторов или информации (credentials) через SOCK_STREAM
необходимо передать/принять, по меньшей мере, один байт не дополнительных данных в
одном из вызовов: sendmsg(2) или recvmsg(2).

В потоковых доменных сокетах UNIX отсутствует такое понятие как внеполосные
данные.

структуры адреса будет отсутствовать конечный null.

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

Приложения, которые получают адрес сокета могут содержать код (переносимый) для
обработки случая, когда нет конечного null в sun_path, учитывая фактическое
количество пригодных байт в пути:

strnlen(addr.sun_path, addrlen - offsetof(sockaddr_un, sun_path))

Или же приложение может перед получением адреса сокета выделить буфер размера
sizeof(struct sockaddr_un)+1, который будет обнулён перед возвращением.
Возвращающий вызов может задать в addrlen значение sizeof(struct sockaddr_un), и
дополнительный нулевой байт здесь будет конечным null в строке, возвращаемой в
sun_path:

void *addrp;

addrlen = sizeof(struct sockaddr_un);
addrp = malloc(addrlen + 1);
if (addrp == NULL)
/* обработка ошибки */ ;
memset(addrp, 0, addrlen + 1);

if (getsockname(sfd, (struct sockaddr *) addrp, &addrlen)) == -1)
/* обработка ошибки */ ;

printf("sun_path = %s\n", ((struct sockaddr_un *) addrp)->sun_path);

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



ПРИМЕР


В следующем коде демонстрируется использование пакето-упорядочивающих сокетов для
локального межпроцессного обмена. Он состоит из двух программ. Программа-сервер
ждёт подключения программы-клиента. Клиент посылает свой каждый аргумент командной
строки в виде отдельного сообщения. Сервер считает входящие сообщения как целые
числа и складывает их. Клиент посылает строку-команду «END». Сервер посылает
ответное сообщение, содержащее сумму чисел клиента. Клиент печатает сумму и
завершает работу. Сервер ждёт подключение следующего клиента. Для остановки
сервера, клиент вызывается с аргументом командной строки «DOWN».

Следующий вывод был записан при работе сервера в фоновом режиме и повторяющемся
запуске клиента. Выполнение программы-сервера завершилось после получения им
команды «DOWN».

Пример вывода
$ ./server &
[1] 25887
$ ./client 3 4
Результат = 7
$ ./client 11 -5
Результат = 6
$ ./client DOWN

#define SOCKET_NAME "/tmp/9Lq7BNBnBycd6nxy.socket"
#define BUFFER_SIZE 12

/*
* Файл server.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "connection.h"

int
main(int argc, char *argv[])
{
struct sockaddr_un name;
int down_flag = 0;
int ret;
int connection_socket;
int data_socket;
int result;
char buffer[BUFFER_SIZE];

/*
* Удалить сокет, оставшийся после последнего
* некорректного завершения программы.
*/

unlink(SOCKET_NAME);

/* Создание локального сокета. */

connection_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (connection_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

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

memset(&name, 0, sizeof(struct sockaddr_un));

/* Привязываем сокет к имени сокета. */

name.sun_family = AF_UNIX;
strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1);

ret = bind(connection_socket, (const struct sockaddr *) &name,
sizeof(struct sockaddr_un));
if (ret == -1) {

ret = listen(connection_socket, 20);
if (ret == -1) {
perror("listen");
exit(EXIT_FAILURE);
}

/* Основной цикл обработки подключений. */

for (;;) {

/* Ожидание входящих подключений. */

data_socket = accept(connection_socket, NULL, NULL);
if (data_socket == -1) {
perror("accept");
exit(EXIT_FAILURE);
}

result = 0;
for(;;) {

/* Ожидание следующего пакета с данными. */

ret = read(data_socket, buffer, BUFFER_SIZE);
if (ret == -1) {
perror("read");
exit(EXIT_FAILURE);
}

/* Проверяем, что буфер завершается 0. */

buffer[BUFFER_SIZE - 1] = 0;

/* Обработка команд. */

if (!strncmp(buffer, "DOWN", BUFFER_SIZE)) {
down_flag = 1;
break;
}

if (!strncmp(buffer, "END", BUFFER_SIZE)) {
break;
}

/* Добавляем полученную команду. */

result += atoi(buffer);
}

/* Отправка результата. */

sprintf(buffer, "%d", result);
ret = write(data_socket, buffer, BUFFER_SIZE);

if (ret == -1) {
perror("write");

if (down_flag) {
break;
}
}

close(connection_socket);

/* Удаляем сокет. */

unlink(SOCKET_NAME);

exit(EXIT_SUCCESS);
}

/*
* Файл client.c
*/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "connection.h"

int
main(int argc, char *argv[])
{
struct sockaddr_un addr;
int i;
int ret;
int data_socket;
char buffer[BUFFER_SIZE];

/* Создание локального сокета. */

data_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (data_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

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

memset(&addr, 0, sizeof(struct sockaddr_un));

/* соединяем сокет с адресом сокета */

addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);

ret = connect (data_socket, (const struct sockaddr *) &addr,
ret = write(data_socket, argv[i], strlen(argv[i]) + 1);
if (ret == -1) {
perror("write");
break;
}
}

/* Отправка результата. */

strcpy (buffer, "END");
ret = write(data_socket, buffer, strlen(buffer) + 1);
if (ret == -1) {
perror("write");
exit(EXIT_FAILURE);
}

/* Получение результата. */

ret = read(data_socket, buffer, BUFFER_SIZE);
if (ret == -1) {
perror("read");
exit(EXIT_FAILURE);
}

/* Проверяем, что буфер завершается 0. */

buffer[BUFFER_SIZE - 1] = 0;

printf("Result = %s\n", buffer);

/* Закрытие сокета. */

close(data_socket);

exit(EXIT_SUCCESS);
}

Пример использования SCM_RIGHTS приведён в cmsg(3).



СМОТРИТЕ ТАКЖЕ


recvmsg(2), sendmsg(2), socket(2), socketpair(2), cmsg(3), capabilities(7),
credentials(7), socket(7), udp(7)



Категория: (7) Различные описания, соглашения и прочее | Просмотров: 562 | Добавил: Администратор | Рейтинг: 0.0/0
Всего комментариев: 0
avatar