12 if (cred.cmcred_ngroups == 0) {
13 printf("(no credentials returned)\n");
14 } else {
15 printf("PID of sender = %d\n", cred.cmcred_pid);
16 printf("real user ID = %d\n", cred.cmcred_uid);
17 printf("real group ID = %d\n", cred.cmcred_gid);
18 printf("effective user ID = %d\n", cred.cmcred_euid);
19 printf("%d groups:", cred.cmcred_ngroups - 1);
20 for (i = 1; i < cred.cmcred_ngroups; i++)
21 printf(" %d", cred.cmcred_groups[i]);
22 printf("\n");
23 }
24 Writen(sockfd, buf, n);
25 }
26 if (n < 0 && errno == EINTR)
27 goto again;
28 else if (n < 0)
29 err_sys("str_echo: read error");
30 }
11-23
Если идентифицирующие данные возвращаются, они выводятся.
24-25
Оставшаяся часть цикла не меняется. Этот код считывает строки от клиента и затем отправляет их обратно клиенту.
Наш клиент, представленный в листинге 15.4, остается практически неизменным. Мы добавляем передачу пустой структуры
cmsgcred
при вызове
sendmsg
, которая заполняется ядром.
Перед запуском клиента определим свои личные данные командой
id
:
freebsd % <b>id</b>
uid=1007(andy) gid=1007(andy) groups=1007(andy), 0(wheel)
Если мы запустим сервер в одном окне, а клиент в другом, то для сервера после однократного выполнения клиента получим представленный ниже вывод.
freebsd % <b>unixstrserv02</b>
PID of sender = 26881
real user ID = 1007
real group ID = 1007
effective user ID = 1007
2 groups: 1007 0
Информация выводится только после отправки клиентом данных серверу. Мы видим, что сведения соответствуют тем, которые были получены командой
id
.
15.9. Резюме
Доменные сокеты Unix являются альтернативой IPC, когда клиент и сервер находятся на одном узле. Преимущество использования доменных сокетов Unix перед некоторой формой IPC состоит в том, что используемый API практически идентичен клиент-серверному сетевому соединению. Преимущество использования доменных сокетов Unix перед TCP, когда клиент и сервер находятся на одном узле, заключается в повышенной производительности доменных сокетов Unix относительно TCP во многих реализациях.
Мы изменили наш эхо-сервер и эхо-клиент TCP и UDP для использования доменных протоколов Unix, и единственным главным отличием оказалась необходимость при помощи функции
bind
связывать полное имя с клиентским сокетом UDP так, чтобы серверу UDP было куда отправлять ответы.
Передача дескрипторов между клиентами и серверами, находящимися на одном узле, — это мощная технология, которая используется при работе с доменными сокетами Unix. Мы показали пример передачи дескриптора от дочернего процесса обратно родительскому процессу в разделе 15.7. В разделе 28.7 мы покажем пример, в котором клиент и сервер не будут родственными, а в разделе 30.9 — другой пример, когда дескриптор передается от родительского процесса дочернему.
Упражнения
1. Что произойдет, если доменный сервер Unix вызовет функцию
unlink
после вызова функции
bind
?
2. Что произойдет, если доменный сервер Unix при завершении не отсоединит с помощью функции
unlink
свое известное полное имя, а клиент будет пытаться с помощью функции
connect
соединиться с сервером через некоторое время после того, как тот завершит работу?
3. Измените листинг 11.5 так, чтобы после того как будет выведен адрес протокола собеседника, вызывалась бы функция
sleep(5)
, а также чтобы вывести число байтов, возвращаемых функцией
read
всякий раз, когда она возвращает положительное значение. Измените листинг 11.8 так, чтобы для каждого байта результата, отправляемого клиенту, вызывалась функция
write
. (Мы обсуждаем подобные изменения в решении упражнения 1.5.) Запустите клиент и сервер на одном узле, используя TCP. Сколько байтов считывает клиент с помощью функции
read
?
Запустите клиент и сервер на одном узле, используя доменный сокет Unix. Изменилось ли что-нибудь?
Теперь для сервера вместо функции write вызовите функцию
send
и задайте флаг
MSG_EOR
(чтобы выполнить это упражнение, вам нужно использовать Беркли-реализацию). Запустите клиент и сервер на одном узле, используя доменный сокет Unix. Изменилось ли что-нибудь?
4. Напишите программу, определяющую значения, показанные в табл. 4.6. Один из подходов — создать потоковый канал и затем с помощью функции
fork
разветвить родительский и дочерний процессы. Родительский процесс входит в цикл
for
, увеличивая на каждом шаге значение
backlog
от 0 до 14. Каждый раз при прохождении цикла родительский процесс сначала записывает значение
backlog
в потоковый канал. Дочерний процесс читает это значение, создает прослушиваемый сокет, связанный с адресом закольцовки, и присваивает
backlog
считанное значение. Затем дочерний процесс делает запись в потоковый канал просто для того, чтобы сообщить родительскому процессу о своей готовности. Затем родительский процесс пытается установить как можно больше соединений, задав предварительно аргумент функции
alarm
равным 2 с, поскольку при достижении предельного значения
backlog
вызов функции connect заблокируется, и отправляет еще раз сегмент
SYN
. Дочерний процесс никогда не вызывает функцию
accept
, что позволяет ядру установить в очередь все соединения с родительским процессом. Когда истекает время ожидания родительского процесса (аргумент функции
alarm
, в данном случае 2 с), по счетчику цикла он может определить, какая по счету функция
connect
соответствует предельному значению
backlog
. Затем родительский процесс закрывает свои сокеты и пишет следующее новое значение в потоковый канал для дочернего процесса. Когда дочерний процесс считывает новое значение, он закрывает прежний прослушиваемый сокет и создает новый, заново начиная процедуру.