Выбрать главу

Запомните несколько дополнительных правил, действующих при чтении и записи данных в программные каналы и FIFO.

■ При попытке считать больше данных, чем в данный момент содержится в программном канале или FIFO, возвращается только имеющийся объем данных. Нужно предусмотреть обработку ситуации, в которой функция read возвращает меньше данных, чем было запрошено.

■ Если количество байтов, направленных на запись функции write, не превышает значения PIPE_BUF (ограничение, устанавливаемое стандартом Posix, о котором более подробно рассказывается в разделе 4.11), то ядро гарантирует атомарность операции записи. Это означает, что если два процесса запишут данные в программный канал или FIFO приблизительно одновременно, то в буфер будут помещены сначала все данные от первого процесса, а затем от второго, либо наоборот. Данные от двух процессов при этом не будут смешиваться. Однако если количество байтов превышает значение PIPEBUF, атомарность операции записи не гарантируется.

ПРИМЕЧАНИЕ

Posix.1 требует, чтобы значение PIPE_BUF равнялось по меньшей мере 512. Характерные значения, встречающиеся на практике, лежат в диапазоне от 1024 (BSD/OS 3.1) до 5120 байт (Solaris 2.6). В разделе 4.11 приведен текст программы, выводящей значение этой константы.

■ Установка флага O_NONBLOCK не влияет на атомарность операции записи в про-грaммный канал или FIFO — она определяется исключительно объемом посылаемых данных в сравнении с величиной PIPE_BUF. Однако если для прогрaммнoгo канала или FIFO отключена блокировка, возвращаемое функцией write значение зависит от количества байтов, отправленных на запись, и наличия свободного места в пpoгрaммнoм канале или FIFO. Если количество байтов не превышает величины PIPE_BUF, то:

 □ Если в канале достаточно места для записи требуемого количества данных, они будут переданы все сразу.

 □ Если места в пpoгрaммнoм канале или FIFO недостаточно для записи требуемого объема данных, происходит немедленное завершение работы функции с возвратом ошибки EAGAIN. Поскольку установлен флаг O_NONBLOCK, процесс не может быть заблокирован, но в то же время ядро не может принять лишь часть данных, так как при этом невозможно гарантировать атомарность операции записи. Поэтому ядро возвращает ошибку, сообщающую процессу о необходимости попытаться произвести запись еще раз.

■ Если количество байтов превышает значение PIPE_BUF, то:

 □ Если в программном канале или FIFO есть место хотя бы для одного байта, ядро передает в буфер ровно столько данных, сколько туда может поместиться, и это переданное количество возвращается функцией write.

 □ Если в программном канале или FIFO свободное место отсутствует, происходит немедленное завершение работы с возвратом ошибки EAGAIN.

■ При записи в программный канал или FIFO, не открытый для чтения, ядро посылает сигнал SIGPIPE:

 □ Если процесс не принимает (catch) и не игнорирует SIGPIPE, выполняется действие по умолчанию — завершение работы процесса.

 □ Если процесс игнорирует сигнал SIGPIPE или перехватывает его и возвращается из подпрограммы его обработки, write возвращает ошибку с кодом EPIPE.

ПРИМЕЧАНИЕ

SIGPIPE считается синхронным сигналом, что означает, что он привязан к конкретному программному потоку, а именно тому, который вызвал функцию write. Простейшим способом обработки сигнала является его игнорирование (установка SIG_IGN) и предоставление функции write возможности вернуть ошибку с кодом EPIPE. В приложении всегда должна быть предусмотрена обработка ошибок, возвращаемых функцией write, а вот определить, что процесс был завершен сигналом SIGPIPE, сложнее. Если сигнал не перехватывается, придется посмотреть на статус завершения работы процесса (termination status) из интерпретатора команд, чтобы узнать, что процесс был принудительно завершен сигналом и каким именно сигналом. В разделе 5.13 [24] о сигнале SIGPIPE рассказывается более подробно.

4.8. Один сервер, несколько клиентов

Преимущества канала FIFO проявляются более явно в том случае, когда сервер представляет собой некоторый длительно функционирующий процесс (например, демон, наподобие описанного в главе 12 [24]), не являющийся родственным клиенту. Демон создает именованный канал с вполне определенным известным именем, открывает его на чтение, а запускаемые впоследствии клиенты открывают его на запись и отправляют демону команды и необходимые данные. Односторонняя связь в этом направлении (от клиента к серверу) легко реализуется с помощью FIFO, однако необходимость отправки данных в обратную сторону (от сервера к клиенту) усложняет задачу. Рисунок 4.12 иллюстрирует прием, применяемый в этом случае. 

Рис. 4.12. Один сервер, несколько клиентов

Сервер создает канал с известным полным именем, в данном случае /tmp/fifо.serv. Из этого канала он считывает запросы клиентов. Каждый клиент при запуске создает свой собственный канал, полное имя которого определяется его идентификатором процесса. Клиент отправляет свой запрос в канал сервера с известным именем, причем запрос этот содержит идентификатор процесса клиента и имя файла, отправку которого клиент запрашивает у сервера. В листинге 4.10 приведен текст программы сервера.

Листинг 4.10. Сервер, обслуживающий несколько клиентов с помощью канала FIFO

//fifocliserv/mainserver.с

1  #include "fifo.h"

2  void server(int, int);

3  int

4  main(int argc, char **argv)

5  {

6   int readfifo, writefifo, dummyfd, fd;

7   char *ptr, buff[MAXLINE], fifoname[MAXLINE];

8   pid_t pid;

9   ssize_t n;

10  /* создание FIFO сервера с известным именем. ОК, если уже существует */

11  if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))

12   err_sys("can't create %s", SERV_FIFO);

13  /* открытие FIFO-cepвepa на чтение */

14  readfifo = Open(SERV_FIFO, O_RDONLY, 0);

15  dummyfd = Open(SERV_FIFO, O_WRONLY, 0); /* не используется */

16  while ((n = Readline(readfifo, buff, MAXLINE)) > 0) {

17   if (buff[n-1] == '\n')

18    n--; /* delete newline from readline() */

19   buff[n] = '\0'; /* полное имя, завершаемое 0 */

20   if ((ptr = strchr(buff, ' ')) == NULL) {

21    err_msg("bogus request: ls", buff);

22    continue;

23   }

24   *ptr++ = 0; /* идентификатор процесса, указатель на имя файла */

25   pid = atol(buff);

26   snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long) pid);

27   if ( (writefifo = open(fifoname, O_WRONLY, 0)) < 0) {

28    err_msg("cannot open: ls", fifoname);

29    continue;

30   }