Шрифт:
Интервал:
Закладка:
Здесь pathname — обычное для Unix полное имя файла, которое и будет именем FIFO.
Аргумент mode указывает битовую маску разрешений доступа к файлу, аналогично второму аргументу команды open. В табл. 2.3 приведены шесть констант, определенных в заголовке <sys/stat.h>. Эти константы могут использоваться для задания разрешений доступа и к FIFO.
Функция mkfifo действует как open, вызванная с аргументом O_CREAT | O_EXCL. Это означает, что создается новый канал FIFO или возвращается ошибка EEXIST, в случае если канал с заданным полным именем уже существует. Если не требуется создавать новый канал, вызывайте open вместо mkfifo. Для открытия существующего канала или создания нового в том случае, если его еще не существует, вызовите mkfifo, проверьте, не возвращена ли ошибка EEXIST, и если такое случится, вызовите функцию open.
Команда mkfifо также создает канал FIFO. Ею можно пользоваться в сценариях интерпретатора или из командной строки.
После создания канал FIFO должен быть открыт на чтение или запись с помощью либо функции open, либо одной из стандартных функций открытия файлов из библиотеки ввода-вывода (например, fopen). FIFO может быть открыт либо только на чтение, либо только на запись. Нельзя открывать канал на чтение и запись, поскольку именованные каналы могут быть только односторонними.
При записи в программный канал или канал FIFO вызовом write данные всегда добавляются к уже имеющимся, а вызов read считывает данные, помещенные в программный канал или FIFO первыми. При вызове функции lseek для программного канала или FIFO будет возвращена ошибка ESPIPE.
Пример
Переделаем программу, приведенную в листинге 4.1, таким образом, чтобы использовать два канала FIFO вместо двух программных каналов. Функции client и server останутся прежними; отличия появятся только в функции main, новый текст которой приведен в листинге 4.6.
Листинг 4.6. Функция main приложения клиент-сервер, использующего две очереди//pipe/mainfifo.c
1 #include "unpipc.h"
2 #define FIFO1 "/tmp/fifo.1"
3 #define FIFO2 "/tmp/fifo.2"
4 void client(int, int), server(int. int);
5 int
6 main(int argc, char **argv)
7 {
8 int readfd, writefd;
9 pid_t childpid;
10 /* создание двух FIFO, если существуют – OK */
11 if ((mkfifo(FIF01, FILE_MODE) < 0) && (errno != EEXIST))
12 err_sys("can't create %s", FIF01);
13 if ((mkfifo(FIF02, FILE_MODE) < 0) && (errno != EEXIST)) {
14 unlink(FIF01);
15 err_sys("can't create %s", FIF02);
16 }
17 if ((childpid = Fork()) == 0) { /* child */
18 readfd = Open(FIF01, O_RDONLY, 0);
19 writefd = Open(FIF02, O_WRONLY, 0);
20 server(readfd, writefd);
21 exit(0);
22 }
23 /* родительский процесс */
24 writefd = Open(FIF01, O_WRONLY, 0);
25 readfd = Open(FIF02, O_RDONLY, 0);
26 client(readfd, writefd);
27 waitpid(childpid, NULL, 0); /* ожидание завершения дочернего процесса */
28 Close(readfd):
29 Close(writefd);
30 Unlink(FIF01);
31 Unlink(FIF02);
32 exit(0);
33 }
Создание двух FIFO10-16 В файловой системе в каталоге /tmp создается два канала. Если какой-либо из них уже существует — ничего страшного. Константа FILE_MODE определена в нашем заголовке unpiрс.h (листинг В.1) как
#define FILEMODE(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
/* разрешения по умолчанию для вновь создаваемых файлов */
При этом владельцу файла разрешается чтение и запись в него, а группе и прочим пользователям — только чтение. Эти биты разрешений накладываются на маску режима доступа создаваемых файлов (file mode creation mask) процесса.
17-27 Далее происходит вызов fork, дочерний процесс вызывает функцию server (листинг 4.3), а родительский процесс вызывает функцию client (листинг 4.2). Перед вызовом этих функций родительский процесс открывает первый канал на запись, а второй на чтение, в то время как дочерний процесс открывает первый канал на чтение, а второй — на запись. Картина аналогична примеру с каналами и иллюстрируется рис. 4.11.
Рис. 4.11. Приложение клиент-сервер, использующее две очереди
Изменения по сравнению с примером, в которым использовались программные каналы, следующие:
■ Для создания и открытия программного канала требуется только один вызов — pipe. Для создания и открытия FIFO требуется вызов mkfifo и последующий вызов open.
■ Программный канал автоматически исчезает после того, как будет закрыт последним использующим его процессом. Канал FIFO удаляется из файловой системы только после вызова unlink. Польза от лишнего вызова, необходимого для создания FIFO, следующая: канал FIFO получает имя в файловой системе, что позволяет одному процессу создать такой канал, а другому открыть его, даже если последний не является родственным первому. С программными каналами это неосуществимо.
В программах, некорректно использующих каналы FIFO, могут возникать неочевидные проблемы. Рассмотрим, например, листинг 4.6: если поменять порядок двух вызовов функции open в породившем процессе, программа перестанет работать. Причина в том, что чтение из FIFO блокирует процесс, если канал еще не открыт на запись каким-либо другим процессом. Действительно, если мы меняем порядок вызовов open в породившем процессе, и породивший, и порожденный процессы открывают канал на чтение, притом что на запись он еще не открыт, так что оба процесса блокируются. Такая ситуация называется блокированием, или зависанием (deadlock). Она будет рассмотрена подробно в следующем разделе.
Пример: неродственные клиент и сервер
В листинге 4.6 клиент и сервер все еще являлись родственными процессами. Переделаем этот пример так, чтобы родство между ними отсутствовало. В листинге 4.7 приведен текст программы-сервера. Текст практически идентичен той части программы из листинга 4.6, которая относилась к серверу.
Содержимое заголовка fifо.h приведено в листинге 4.8. Этот файл определяет имена двух FIFO, которые должны быть известны как клиенту, так и серверу.
В листинге 4.9 приведен текст программы-клиента, которая не слишком отличается от части программы из листинга 4.6, относящейся к клиенту. Обратите внимание, что именно клиент, а не сервер удаляет канал FIFO по завершении работы, потому что последние операции с этим каналом выполняются им.
Листинг 4.7. Функция main независимого сервера//pipe/server_main.c
1 #include "fifo.h"
2 void server(int, int);
3 int
4 main(int argc, char **argv)
5 {
6 int readfd, writefd;
7 /* создание двух FIFO. OK, если они существуют */
8 if ((mkfifo(FIF01, FILE_MODE) < 0) && (errno != EEXIST))
9 err_sys("can't create %s", FIF01);
10 if ((mkfifo(FIF02, FILE MODE) < 0) && (errno != EEXIST)) {
11 unlink(FIF01);
12 err_sys("can't create %s", FIF02);
13 }
14 readfd = Open(FIF01, O_RDONLY, 0);
15 writefd = Open(FIFO2, O_WRONLY, 0);
16 server(readfd, writefd);
17 exit(0);
18 }
Листинг 4.8. Заголовочный файл fifo.h, используемый и клиентом, и сервером//pipe/fifo.h
1 #include "unpipc.h"
2 #define FIFO1 "/tmp/fifo.1"
3 #define FIFO2 "/tmp/fifo.2"
Листинг 4.9. Функция main независимого клиента//pipe/client_main.c
1 #include "fifo.h"
2 void client(int, int);
3 int
4 main(int argc, char **argv)
5 {
6 int readfd, writefd;
7 writefd = Open(FIFO1, O_WRONLY, 0);
8 readfd = Open(FIFO2, O_RDONLY, 0);
9 client(readfd, writefd);
10 Close(readfd);
11 Close(writefd);
12 Unlink(FIFO1);
13 UnLink(FIFO2);
14 exit(0);
15 }
ПРИМЕЧАНИЕ
Для программных каналов и каналов FIFO ядро ведет подсчет числа открытых дескрипторов, относящихся к ним, поэтому безразлично, кто именно вызовет unlink — клиент или сервер. Хотя эта функция и удаляет файл из файловой системы, она не влияет на открытые в момент ее выполнения дескрипторы. Однако для других форм IPC, таких как очереди сообщений стандарта System V, счетчик отсутствует, и если сервер удалит очередь после записи в нее последнего сообщения, она может быть удалена еще до того, как клиент это сообщение считает.
Для запуска клиента и сервера запустите сервер в фоновом режиме:
% server_fifo &
а затем запустите клиент. Можно было сделать и по-другому: запускать только программу-клиент, которая запускала бы сервер с помощью fork и exec. Клиент мог бы передавать серверу имена FIFO в качестве аргументов командной строки в команде exec, вместо того чтобы обе программы считывали их из заголовка. Но в этом случае сервер являлся бы дочерним процессом и проще было бы обойтись программным каналом.
- Delphi. Учимся на примерах - Сергей Парижский - Программирование
- Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ - Хелен Борри - Программирование
- Программирование на Python с нуля - Максим Кононенко - Программирование
- C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц - Программирование
- Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп - Программирование