Глава 15
Доменные протоколы Unix
15.1. Введение
Доменные протоколы Unix — это не набор протоколов, а способ связи клиентов и серверов на отдельном узле, использующий тот же API, который используется для клиентов и серверов на различных узлах, — сокеты или XTI. Доменные протоколы Unix представляют альтернативу методам IPC (Interprocess Communications — взаимодействие процессов), которым посвящен второй том[2] этой серии, применяемым, когда клиент и сервер находятся на одном узле. Подробности действительной реализации доменных сокетов Unix в ядре, происходящем от Беркли, приводятся в третьей части [112].
В домене Unix предоставляются два типа сокетов: потоковые (аналогичные сокетам TCP) и дейтаграммные (аналогичные сокетам UDP). Хотя предоставляется также и символьный сокет, но его семантика никогда не документировалась, он не используется никакой из известных автору программ и не определяется в POSIX.
Доменные сокеты Unix используются по трем причинам.
1. В реализациях, происходящих от Беркли, доменные сокеты Unix часто вдвое быстрее сокетов TCP, когда оба собеседника находятся на одном и том же узле [112, с. 223–224]. Есть приложение, которое использует это преимущество: X Window System. Когда клиент X11 запускается и открывает соединение с сервером X11, клиент проверяет значение переменной окружения DISPLAY, которая задает имя узла сервера, окно и экран. Если сервер находится на том же узле, что и клиент, клиент открывает потоковое соединение с сервером через доменный сокет Unix, в противном случае клиент открывает соединение TCP.
2. Доменные сокеты Unix используются при передаче дескрипторов между процессами на одном и том же узле. Пример мы приводим в разделе 15.7.
3. Более новые реализации доменных сокетов Unix предоставляют регистрационные данные клиента (идентификатор пользователя и идентификаторы группы) серверу, что может служить дополнительной проверкой безопасности. Мы покажем это в разделе 15.8.
Адреса протоколов, используемые для идентификации клиентов и серверов в домене Unix, — это полные имена в обычной файловой системе. Вспомните, что IPv4 использует комбинацию 32-разрядных адресов и 16-разрядных номеров портов для своих адресов протоколов, а IPv6 для своих адресов протоколов использует комбинацию 128-разрядных адресов и 16-разрядных номеров портов. Эти полные имена не являются обычными именами файлов Unix: в общем случае мы не можем читать из этих файлов или записывать в них. Это может делать только программа, связывающая полное имя с доменным сокетом Unix.
15.2. Структура адреса доменного сокета Unix
В листинге 15.1[1] показана структура адреса доменного сокета Unix, задаваемая включением заголовочного файла
<sys/un.h>
.
Листинг 15.1. Структура адреса доменного сокета Unix: sockaddr_un
struct sockaddr_un {
uint8_t sun_len;
sa_family_t sun_family; /* AF_LOCAL */
char sun_path[104]; /* полное имя, оканчивающееся нулем */
};
ПРИМЕЧАНИЕ
POSIX не задает длину массива sun_path и предупреждает, что разработчику приложения не следует делать предположений об этой длине. Воспользуйтесь оператором sizeof для определения длины массива во время выполнения программы. Убедитесь, что полное имя помещается в этот массив. Длина, скорее всего, будет где-то между 92 и 108. Причина этих ограничений — в артефакте реализации, возникшем еще в 4.2BSD, где требовалось, чтобы структура помещалась в 128-байтовый буфер памяти ядра mbuf.
Полное имя, хранимое в символьном массиве
sun_path
, должно завершаться нулем. Имеется макрос
SUN_LEN
, который получает указатель на структуру
sockaddr_un
и возвращает длину структуры, включая число непустых байтов в полном имени. Неопределенный адрес обозначается пустой строкой, то есть элемент
sun_path[0]
должен быть равен нулю. Это эквивалент константы
INADDR_ANY
протокола IPv4 и константы
IN6ADDR_ANY_INIT
протокола IPv6 для домена Unix.
ПРИМЕЧАНИЕ
В POSIX доменным протоколам Unix дали название «локального IPC», чтобы не подчеркивать зависимость от операционной системы Unix. Историческая константа AF_UNIX становится константой AF_LOCAL. Тем не менее мы будем продолжать использовать термин «домен Unix», так как он стал именем де-факто, независимо от соответствующей операционной системы. Кроме того, несмотря на попытку POSIX исключить зависимость от операционной системы, структура адреса сокета сохраняет суффикс _un!
Пример: функция bind и доменный сокет Unix
Программа, показанная в листинге 15.2, создает доменный сокет Unix, с помощью функции
bind
связывает с ним полное имя и затем вызывает функцию
getsockname
и выводит это полное имя.
Листинг 15.2. Связывание полного имени с доменным сокетом Unix
unixdomain/unixbind.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int sockfd;
6 socklen_t len;
7 struct sockaddr_un addr1, addr2;
8 if (argc != 2)
9 err_quit("usage: unixbind <pathname>");
10 sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
11 unlink(argv[1]); /* игнорируем возможную ошибку */
12 bzero(&addr1, sizeof(addr1));
13 addr1.sun_family = AF_LOCAL;
14 strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1);
15 Bind(sockfd, (SA*)&addr1, SUN_LEN(&addr1));
16 len = sizeof(addr2);
17 Getsockname(sockfd, (SA*)&addr2, &len);
18 printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
19 exit(0);
20 }
Удаление файла
11
Полное имя, которое функция
bind
должна связать с сокетом, — это аргумент командной строки. Если это полное имя уже существует в файловой системе, при выполнении функции
bind
возникает ошибка. Следовательно, мы вызываем функцию
unlink
, чтобы удалить файл в том случае, если он уже существует. Если его не существует, функция
unlink
возвращает ошибку, которую мы игнорируем.