| UNIX(7) | Miscellaneous Information Manual | UNIX(7) |
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]; /* имя пути */
};
The sun_family field always contains AF_UNIX. On Linux, sun_path is 108 bytes in size; see also BUGS, below.
Various system calls (for example, bind(2), connect(2), and sendto(2)) take a sockaddr_un argument as input. Some other system calls (for example, getsockname(2), getpeername(2), recvfrom(2), and accept(2)) return an argument of this type.
В sockaddr_un структуре различают три типа адресов:
offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1
При привязке сокета к пути для максимальной переносимости и простоте кодирования нужно учесть несколько правил:
offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path)+1
Есть несколько реализаций по работе с адресами доменных сокетов 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)).
Права на сокеты не учитываются у абстрактных сокетов: umask(2) процесса не учитывается при подключении к абстрактному сокету как и изменение владельца и прав доступа к объекту (посредством fchown(2) и fchmod(2)) не влияют на доступность сокета.
Абстрактные сокеты автоматически исчезают при закрытии всех открытых ссылок на них.
Пространство имён абстрактных сокетов является непереносимым расширением Linux.
В силу исторических причин эти параметры сокетов относятся к типу SOL_SOCKET, даже если они относятся к AF_UNIX. Они могут быть установлены с помощью setsockopt(2) и прочитаны с помощью getsockopt(2); тип SOL_SOCKET указывается в качестве семейства сокета.
If a bind(2) call specifies addrlen as sizeof(sa_family_t), or the SO_PASSCRED socket option was specified for a socket that was not explicitly bound to an address, then the socket is autobound to an abstract address. The address consists of a null byte followed by 5 bytes in the character set [0-9a-f]. Thus, there is a limit of 2^20 autobind addresses. (From Linux 2.1.15, when the autobind feature was added, 8 bytes were used, and the limit was thus 2^32 autobind addresses. The change to 5 bytes came in Linux 2.3.15.)
В следующих параграфах описываются специфичные тонкости доменов и неподдерживаемые возможности программного интерфейса сокетов для доменных сокетов 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).
struct ucred {
pid_t pid; /* идентификатор посылающего процесса */
uid_t uid; /* идентификатор пользователя посылающего процесса */
gid_t gid; /* идентификатор группы посылающего процесса */
};
При отправке вспомогательных данных с помощью sendmsg(2) посылаемое сообщение может содержать только по одному элементу каждого типа, из представленных выше.
По крайней мере один байт реальных данных должен быть отправлен при отправке вспомогательных данных. В Linux это требуется для успешной отправки вспомогательных данных через потоковый доменный сокет UNIX. При отправке вспомогательных данных через дейтаграммный доменный сокет UNIX в Linux необязательно отправлять какие-либо реальные сопровождающие данные. Однако переносимые приложения должны также включать, по крайней мере, один байт реальных данных при отправке вспомогательных данных через дейтаграммный сокет.
При получении из потокового сокета вспомогательные данные формируют своего рода барьер для полученных данных. Например, предположим, что отправитель передает так:
Suppose that the receiver now performs recvmsg(2) calls each with a buffer size of 20 bytes. The first call will receive five bytes of data, along with the ancillary data sent by the second sendmsg(2) call. The next call will receive the remaining four bytes of data.
Если место, выделенное для получения входящих вспомогательных данных, слишком маленькое, то вспомогательные данные обрезаются по количеству заголовков, которые влезут в предоставленной буфер (или, в случае списка файловых дескрипторов SCM_RIGHTS, может быть обрезан список файловых дескрипторов). Если для входящих вспомогательных данных буфер не был предусмотрен (т. е., поле msg_control в структуре msghdr, указанное recvmsg(2), равно NULL), то входящие вспомогательные данные отбрасываются. В обоих случаях, в возвращаемом значении recvmsg(2) в msg.msg_flags будет установлен флаг MSG_CTRUNC.
Следующие вызовы ioctl(2) возвращают информацию в аргументе value. Корректный синтаксис:
int value; error = ioctl(unix_socket, ioctl_type, &value);
Значением ioctl_type может быть:
При создании сокетного объекта на уровне сокетов или файловой системы могут генерироваться другие ошибки. За дополнительной информацией обращайтесь к соответствующей справочной странице.
SCM_CREDENTIALS и абстрактное пространство имён появились в Linux 2.2 и не должны использоваться в переносимых программах. Некоторые клоны BSD также поддерживают передачу дополнительной информации (credential), но методы реализации передачи могут серьезно отличаться на разных системах.
Привязка сокета к имени файла создаёт сокет в файловой системе, который должен быть удалён создателем, когда необходимость в нём отпадёт (с помощью unlink(2)). Обычная система ссылок UNIX также подходит для работы с сокетами; сокет может быть удалён в любое время, а реальное удаление из файловой системы будет произведено при закрытии последней на него ссылки.
To pass file descriptors or credentials over a SOCK_STREAM socket, you must send or receive at least one byte of nonancillary data in the same sendmsg(2) or recvmsg(2) call.
В потоковых доменных сокетах UNIX отсутствует такое понятие как внеполосные данные.
When binding a socket to an address, Linux is one of the implementations that append a null terminator if none is supplied in sun_path. In most cases this is unproblematic: when the socket address is retrieved, it will be one byte longer than that supplied when the socket was bound. However, there is one case where confusing behavior can result: if 108 non-null bytes are supplied when a socket is bound, then the addition of the null terminator takes the length of the pathname beyond sizeof(sun_path). Consequently, when retrieving the socket address (for example, via accept(2)), if the input addrlen argument for the retrieving call is specified as sizeof(struct sockaddr_un), then the returned address structure won't have a null terminator in sun_path.
Также, некоторые реализации не требуют наличия конечного 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)
/* Handle error */ ; memset(addrp, 0, addrlen + 1); if (getsockname(sfd, (struct sockaddr *) addrp, &addrlen)) == -1)
/* handle error */ ; 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 Результат = 0 [1]+ Done ./server $
/*
* File connection.h
*/ #ifndef CONNECTION_H #define CONNECTION_H #define SOCKET_NAME "/tmp/9Lq7BNBnBycd6nxy.socket" #define BUFFER_SIZE 12 #endif // include guard
/*
* File server.c
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include "connection.h" int main(void) {
int down_flag = 0;
int ret;
int connection_socket;
int data_socket;
int result;
ssize_t r, w;
struct sockaddr_un name;
char buffer[BUFFER_SIZE];
/* Create local socket. */
connection_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (connection_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/*
* For portability clear the whole structure, since some
* implementations have additional (nonstandard) fields in
* the structure.
*/
memset(&name, 0, sizeof(name));
/* Bind socket to socket name. */
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(name));
if (ret == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
/*
* Prepare for accepting connections. The backlog size is set
* to 20. So while one request is being processed other requests
* can be waiting.
*/
ret = listen(connection_socket, 20);
if (ret == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
/* This is the main loop for handling connections. */
for (;;) {
/* Wait for incoming connection. */
data_socket = accept(connection_socket, NULL, NULL);
if (data_socket == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
result = 0;
for (;;) {
/* Wait for next data packet. */
r = read(data_socket, buffer, sizeof(buffer));
if (r == -1) {
perror("read");
exit(EXIT_FAILURE);
}
/* Ensure buffer is 0-terminated. */
buffer[sizeof(buffer) - 1] = 0;
/* Handle commands. */
if (!strncmp(buffer, "DOWN", sizeof(buffer))) {
down_flag = 1;
continue;
}
if (!strncmp(buffer, "END", sizeof(buffer))) {
break;
}
if (down_flag) {
continue;
}
/* Add received summand. */
result += atoi(buffer);
}
/* Send result. */
sprintf(buffer, "%d", result);
w = write(data_socket, buffer, sizeof(buffer));
if (w == -1) {
perror("write");
exit(EXIT_FAILURE);
}
/* Close socket. */
close(data_socket);
/* Quit on DOWN command. */
if (down_flag) {
break;
}
}
close(connection_socket);
/* Unlink the socket. */
unlink(SOCKET_NAME);
exit(EXIT_SUCCESS); }
/*
* File client.c
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include "connection.h" int main(int argc, char *argv[]) {
int ret;
int data_socket;
ssize_t r, w;
struct sockaddr_un addr;
char buffer[BUFFER_SIZE];
/* Create local socket. */
data_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (data_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/*
* For portability clear the whole structure, since some
* implementations have additional (nonstandard) fields in
* the structure.
*/
memset(&addr, 0, sizeof(addr));
/* Connect socket to socket address. */
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
ret = connect(data_socket, (const struct sockaddr *) &addr,
sizeof(addr));
if (ret == -1) {
fprintf(stderr, "The server is down.\n");
exit(EXIT_FAILURE);
}
/* Send arguments. */
for (int i = 1; i < argc; ++i) {
w = write(data_socket, argv[i], strlen(argv[i]) + 1);
if (w == -1) {
perror("write");
break;
}
}
/* Request result. */
strcpy(buffer, "END");
w = write(data_socket, buffer, strlen(buffer) + 1);
if (w == -1) {
perror("write");
exit(EXIT_FAILURE);
}
/* Receive result. */
r = read(data_socket, buffer, sizeof(buffer));
if (r == -1) {
perror("read");
exit(EXIT_FAILURE);
}
/* Ensure buffer is 0-terminated. */
buffer[sizeof(buffer) - 1] = 0;
printf("Result = %s\n", buffer);
/* Close socket. */
close(data_socket);
exit(EXIT_SUCCESS); }
For examples of the use of SCM_RIGHTS, see cmsg(3) and seccomp_unotify(2).
recvmsg(2), sendmsg(2), socket(2), socketpair(2), cmsg(3), capabilities(7), credentials(7), socket(7), udp(7)
Русский перевод этой страницы руководства разработал(и) Azamat Hackimov <azamat.hackimov@gmail.com>, Dmitriy Ovchinnikov <dmitriyxt5@gmail.com>, Dmitry Bolkhovskikh <d20052005@yandex.ru>, Katrin Kutepova <blackkatelv@gmail.com>, Yuri Kozlov <yuray@komyakino.ru>, Иван Павлов <pavia00@gmail.com> и Kirill Rekhov <krekhov.dev@gmail.com>
Этот перевод является свободной программной документацией; он распространяется на условиях общедоступной лицензии GNU (GNU General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html версии 3 или более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.
Если вы обнаружите какие-либо ошибки в переводе этой страницы руководства, пожалуйста, сообщите об этом разработчику(ам) по его(их) адресу(ам) электронной почты или по адресу списка рассылки русских переводчиков.
| 15 июня 2024 г. | Справочные страницы Linux 6.9.1 |