31 if ((fd = open(ptr, O_RDONLY)) < 0) {
32 /* ошибка, нужно сообщить клиенту */
33 snprintf(buff + n, sizeof(buff) – n, ": can't open, %s\n",
34 strerror(errno));
35 n = strlen(ptr);
36 Write(writefifo, ptr, n);
37 Close(writefifo);
38
39 } else {
40 /* успешное открытие, копируем файл */
41 while ((n = Read(fd, buff, MAXLINE)) > 0)
42 Write(writefifo, buff, n);
43 Close(fd);
44 Close(writefifo);
45 }
46 }
47 }
10-15 Сервер создает канал FIFO с известным именем, обрабатывая ситуацию, когда такой канал уже существует. Затем этот канал открывается дважды: один раз только для чтения, а второй — только для записи. Дескриптор readfifo используется для приема запросов от клиентов, а дескриптор dummyfd не используется вовсе. Причина, по которой нужно открыть канал для записи, видна из табл. 4.1. Если канал не открыть на запись, то при завершении работы очередного клиента этот канал будет опустошаться и сервер будет считывать 0, означающий конец файла. Пришлось бы каждый раз закрывать канал вызовом close, а затем заново открывать его с флагом O_RDONLY, что приводило бы к блокированию демона до подключения следующего клиента. Мы же всегда будем иметь дескриптор, открытый на запись, поэтому функция read не будет возвращать 0, означающий конец файла, при отсутствии клиентов. Вместо этого сервер просто будет блокироваться при вызове read, ожидая подключения следующего клиента. Этот трюк упрощает код программы-сервера и уменьшает количество вызовов open для канала сервера.
При запуске сервера первый вызов open (с флагом O_RDONLY) приводит к блокированию процесса до появления первого клиента, открывающего канал сервера на запись (см. табл. 4.1). Второй вызов open (с флагом O_WRONLY) не приводит к блокированию, поскольку канал уже открыт на запись.
16 Каждый запрос, принимаемый от клиента, представляет собой одну строку, состоящую из идентификатора процесса, пробела и полного имени требуемого файла. Эта строка считывается функцией readline (приведенной в [24, с.79]).
17-26 Символ перевода строки, возвращаемый функцией readline, удаляется. Этот символ может отсутствовать только в том случае, если буфер был заполнен, прежде чем был обнаружен символ перевода строки, либо если последняя введенная строка не была завершена этим символом. Функция strchr возвращает указатель на первый пробел в этой строке, который затем увеличивается на единицу, чтобы он указывал на первый символ полного имени файла, следующего за пробелом. Полное имя канала клиента формируется из его идентификатора процесса, и этот канал открывается сервером на запись.
Открытие файла и отправка его в FIFO клиента
27-44 Оставшаяся часть кода пpoгрaммы-cepвepa аналогична функции server из листинга 4.3. Программа открывает файл; если при этом возникает ошибка — клиенту отсылается сообщение о ней. Если открытие файла завершается успешно, его содержимое копируется в канал клиента. После завершения копирования открытый сервером «конец» (дескриптор) канала клиента должен быть закрыт с помощью функции close, чтобы функция read вернула пpoгрaммe-клиeнтy значение 0 (конец файла). Сервер не удаляет канал клиента; клиент должен самостоятельно позаботиться об этом после приема от сервера символа конца файла. Текст пpoгрaммы-клиeнтa приведен в листинге 4.11.
//fifocliserv/mainclient.с
1 #include "fifo.h"
2 int
3 main(int argc, char **argv)
4 {
5 int readfifo, writefifo;
6 size_t len;
7 ssize_t n;
8 char *ptr, fifoname[MAXLINE], buff[MAXLINE];
9 pid_t pid;
10 /* создание FIFO с включением в его имя PID */
11 pid = getpid();
12 snprintf(fifoname, sizeof(fifoname), "/tmp/fifo,%ld", (long) pid):
13 if ((mkfifo(fifoname, FILE_MODE) < 0) && (errno != EEXIST))
14 err_sys("can't create %s", fifoname);
15 /* инициализация буфера PID и пробелом */
16 snprintf(buff, sizeof(buff), "%ld ", (long) pid);
17 len = strlen(buff);
18 ptr = buff + len;
19 /* считывание полного имени */
20 Fgets(ptr, MAXLINE – len, stdin);
21 len = strlen(buff); /* fgets() гарантирует завершающий 0 */
22 /* открытие FIFO сервера и запись в него полного имени и PID */
23 writefifo = Open(SERV_FIFO, O_WRONLY, 0);
24 Write(writefifo, buff, len);
25 /* открытие созданного FIFO; блокирование до открытия его сервером */
26 readfifo = Open(fifoname, O_RDONLY; 0);
27 /* считывание из канала IPC, запись в stdout */
28 while ((n = Read(readfifo, buff, MAXLINE)) > 0)
29 Write(STDOUT_FILENO, buff, n);
30 Close(readfifo);
31 Unlink(fifoname);
32 exit(0);
33 }
10-14 Идентификатор процесса клиента содержится в имени создаваемого им канала.
15-21 Запрос клиента состоит из его идентификатора процесса, одного пробела, полного имени запрашиваемого им файла и символа перевода строки. Строка запроса формируется в массиве buff, причем имя файла считывается из стандартного потока ввода.
22-24 Клиент открывает канал сервера и записывает в него строку запроса. Если клиент окажется первым с момента запуска сервера, вызов open разблокирует сервер, заблокированный после сделанного им вызова open (с флагом O_RDONLY).
25-31 Ответ сервера считывается из канала и записывается в стандартный поток вывода, после чего канал клиента закрывается и* удаляется.
Сервер может быть запущен в одном из окон, а клиент — в другом, и программа будет работать так, как мы и рассчитывали. Ниже мы приводим только текст, выводимый клиентом:
solaris % mainclient /etc/shadow файл, который нам нельзя читать
/etc/shadow: can't open. Permission denied
solaris % mainclient /etc/inet/ntp.conf файл из двух строк
multicastclient 224.0.1.1
driftfile /etc/inet/ntp.drift
Мы можем также связаться с сервером из интерпретатора команд, поскольку каналы FIFO обладают именами в файловой системе.
solaris % Pid=$$
solaris % mkfifo /tmp/fifo.$Pid
solaris % echo "$Pid /etc/inet/ntp.conf" > /tmp/fifo.serv
solaris % cat < /tmp/fifo.$Pid