- •Часть 4. Локальное взаимодействие процессов
- •Глава 16. Блокирование записей 89
- •12.2. Процессы, потоки и общий доступ к информации
- •12.3. Живучесть объектов ipc
- •12.4. Пространства имен
- •12.5. Действие команд fork, exec и exit на объекты ipc
- •12.6. Комментарии к примерам ipc
- •12.7. Выводы по главе 12
- •12.8. Упражнения по главе 12
- •Глава 13. Именованные и неименованные каналы
- •13.1. Введение
- •13.2. Приложение типа клиент-сервер
- •13.3. Программные каналы
- •13.4. Функции popen и pclose
- •13.5. Именованные каналы (fifo)
- •13.6. Некоторые свойства именованных и неименованных каналов
- •13.7. Один сервер, несколько клиентов
- •13.8. Последовательные и параллельные серверы
- •13.9. Ограничения программных каналов и fifo
- •13.10. Выводы по главе 13
- •13.11. Упражнения по главе 13
- •Глава 14. Программные потоки
- •14.1. Введение
- •14.2. Концепция потоков
- •14.3. Идентификация потоков
- •14.4. Создание потока
- •14.5. Завершение потока
- •Функции управления процессами и потоками
- •14.6. Установка атрибутов потока
- •14.7. Реентерабельность
- •Альтернативные версии функций, безопасные в многопоточной среде
- •14.8. Локальные данные потоков
- •14.9. Принудительное завершение потоков
- •Некоторые точки выхода, определенные стандартом Posix.1
- •14.10. Потоки и сигналы
- •14.11. Выводы по главе 14
- •14.12. Упражнения по главе 14 Глава 15. Средства синхронизации потоков
- •15.1. Введение
- •15.2. Взаимные исключения: установка и снятие блокировки
- •15.2.1. Схема производитель-потребитель
- •15.2.2. Блокирование и опрос
- •15.2.3. Предотвращение тупиковых ситуаций
- •15.3. Условные переменные
- •15.3.1. Ожидание и сигнализация
- •15.3.2. Исключение состояния гонок
- •15.4. Блокировки чтения-записи
- •15.5. Атрибуты средств синхронизации потоков
- •15.5.1. Атрибуты взаимных исключений
- •Поведение взаимных исключений различных типов
- •15.5.2. Атрибуты условных переменных
- •15.5.3. Атрибуты блокировок чтения-записи
- •15.6. Выводы по главе 15
- •15.7. Упражнения по главе 15
- •Глава 16. Блокирование записей
- •16.1. Введение
- •16.2. Блокирование записей и файлов
- •16.3. Блокирование записей с помощью fcntl по стандарту Posix
- •16.4. Рекомендательная блокировка
- •16.5. Обязательная блокировка
- •16.6. Приоритет чтения и записи Выводы по главе 16
- •Упражнения по главе 16 Глава 17. System V ipc
- •17.1. Введение
- •17.2. Ключи типа key_t и функция ftok
- •17.3. Структура ipc_perm
- •17.4. Создание и открытие каналов ipc
- •17.5. Разрешения ipc
- •17.6. Программы ipcs и ipcrm
- •17.7. Ограничения ядра
- •17.8. Выводы по главе 17
- •17.9. Упражнения по главе 17
- •Глава 18. Очереди сообщений System V
- •18.1. Введение
- •18.2. Функция msgget
- •18.3. Функция msgsnd
- •18.4. Функция msgrcv
- •18.5. Функция msgctl
- •18.6. Пример программы клиент-сервер
- •18.7. Мультиплексирование сообщений
- •18.7.1. Пример: одна очередь на приложение
- •18.7.2. Пример: одна очередь для каждого клиента
- •18.8. Ограничения, накладываемые на очереди сообщений
- •18.9. Выводы по главе 18
- •18.10. Упражнения по главе 18
- •Глава 19. Семафоры System V
- •19.1. Введение
- •19.2. Функция semget
- •19.3. Функция semop
- •19.4. Функция semctl
- •19. . Ограничения семафоров System V
- •19. . Выводы по главе 19
- •19. . Упражнения по главе 19 Глава 20. Введение в разделяемую память
- •20.1. Введение
- •20.2. Функции mmap, munmap и msync
- •20.3. Увеличение счетчика в отображаемом в память файле
- •20.4. Неименованное отображение в память
- •20.5. Обращение к объектам, отображенным в память
- •20.6. Выводы по главе 20
- •20.7. Упражнения по главе 20
- •Глава 21. Разделяемая память System V
- •21.1. Введение
- •21.2. Функция shmget
- •21.3. Функция shmat
- •21.4. Функция shmdt
- •21.5. Функция shmctl
- •21.6. Ограничения, накладываемые на разделяемую память
- •21.7. Выводы по главе 21
- •21.8. Упражнения по главе 21
18.7. Мультиплексирование сообщений
Наличие поля typeу каждого сообщения в очереди предоставляет две интересные возможности:
Поле typeможет использоваться для идентификации сообщений, позволяя нескольким процессам мультиплексировать сообщения в одной очереди. Например, все сообщения от клиентов серверу имеют одно и то же значение типа, тогда как сообщения сервера клиентам имеют различные значения типов, уникальные для каждого клиента. Естественно, в качестве значения типа сообщения, гарантированно уникального для каждого клиента, можно использовать идентификатор процесса клиента.
Поле typeможет использоваться для установки приоритета сообщений. Это позволяет получателю считывать сообщения в порядке, отличном от принятого для очередей (FIFO). В программных каналах иFIFOданные могли приниматься только в том порядке, в котором они были отправлены. Очереди System V позволяют считывать сообщения в произвольном порядке в зависимости от значений типа сообщений. Более того, можно вызватьmsgrcvс флагомIPC_NOWAITдля считывания сообщений с конкретным типом и немедленного возвращения управления процессу в случае отсутствия таких сообщений.
18.7.1. Пример: одна очередь на приложение
Вспомните наш простой пример с одним процессом-сервером и одним процессом-клиентом. Если применять программные каналы или FIFO, необходимо наличие двух каналов IPC для передачи данных в обоих направлениях, поскольку эти типы IPC являются однонаправленными. Очереди сообщений позволяют передавать данные в обоих направлениях, причем полеtypeможет использоваться для указания адресата (клиента или сервера).
Рассмотрим усложненный вариант: один сервер и несколько клиентов. В этом случае можно использовать значение типа 1, например, для обозначения сообщений от любого клиента серверу. Если клиент передаст серверу свой идентификатор процесса в качестве части сообщения, сервер сможет отсылать клиенту сообщения, используя его идентификатор в качестве значения типа сообщения. Каждый клиент будет использовать свой PID в качестве аргумента typeпри вызовеmsgrcv. На рис. 17.2 приведен пример использования очереди для мультиплексирования этих сообщений между несколькими клиентами и одним сервером.

рис. 17.2
При использовании одного канала IPC одновременно клиентами и сервером всегда существует потенциальная опасность возникновения блокировки (deadlock). В данном случае клиенты могут заполнить очередь своими сообщениями, не давая возможности серверу отправить ответ. В результате клиенты и сервер будут заблокированы при вызове функции msgsnd. Одно из соглашений, исключающих возможность возникновения подобной взаимной блокировки, заключается в том, что сервер должен всегда отключать блокировку записи при помещении очередного сообщения в очередь.
Теперь мы можем переделать наш пример с клиентом и сервером, используя одну очередь сообщений с различными типами для разных адресатов. Эти программы используют следующее соглашение: сообщения с типом 1 адресованы серверу, а все остальные сообщения имеют тип, соответствующий идентификатору процесса-адресата. При этом запрос клиента должен содержать его PID вместе с полным именем запрашиваемого файла, аналогично программе из раздела 13.7.
В листинге 17.4приведен текст программы-сервера. В ней создается единственная очередь сообщений (если она существует, ошибки не возникнет). Идентификатор этой очереди сообщений одновременно является первым и вторым аргументом функцииserver, которая обеспечивает работу сервера. Эта функция представляет собой комбинацию программ из листинга 13.6 – нашего сервера файлов, считывающего идентификатор процесса клиента и полное имя запрашиваемого файла из FIFO, – и листинга 17.2, в котором сервер для общения с клиентами использует очереди сообщений. Обратите внимание, что идентификатор процесса, отправляемый клиентом, используется как тип для всех сообщений, отправляемых сервером этому клиенту. Функцияserverпредставляет собой бесконечный цикл, в котором считываются запросы клиентов, а также отсылаются запрошенные файлы. Этот сервер является последовательным (см. раздел 13.8).
Листинг 17.4. Последовательный сервер файлов, использующий одну очередь сообщений
void server(int readid, int writeid)
{
FILE *fp;
char *ptr;
ssize_t n;
struct mymesg mesg;
char buff[MAXMESGDATA];
for ( ; ; )
{ /* чтение полного имени файла из канала IPC */
if ((n = msgrcv(readid, &mesg, sizeof(mesg.mesg_len) + MAXMESGDATA, 1, 0))
== -1)
{
fprintf(stderr, "Сервер: ошибка чтения запроса клиента из канала IPC:
%s\n", strerror(errno));
exit(1);
}
if (n == sizeof(mesg.mesg_len))
{
fprintf(stderr, "Сервер: получено пустое имя файла, продолжение
работы\n");
continue;
} /* добавляем признак конца строки к полному имени */
mesg.mesg_data[n - sizeof(mesg.mesg_len)] = '\0';
strncpy(buff, mesg.mesg_data, sizeof(mesg.mesg_data));
if ((ptr = strchr(buff, ' ')) == NULL)
{
fprintf(stderr, "Сервер: неправильный запрос: %s\n", buff);
continue;
}
*ptr++ = '\0'; /* нулевой байт вставлен в buff на место пробела */
/* ptr после увеличения (++) указывает на имя запрошенного файла */
mesg.mesg_type = atol(buff); /* для обратных сообщений клиенту */
if ((fp = fopen(ptr, "r")) == NULL)
{ /* сообщаем клиенту об ошибке открытия файла */
snprintf(mesg.mesg_data, sizeof(mesg.mesg_data), "Сервер: ошибка открытия
файла %s: %s\n", ptr, strerror(errno));
mesg.mesg_len = strlen(mesg.mesg_data);
if (msgsnd(writeid, &mesg, sizeof(mesg.mesg_len) + strlen(mesg.mesg_data),
0) == -1)
{
fprintf(stderr, "Сервер: ошибка записи в канал IPC: %s\n",
strerror(errno));
exit(1);
}
}
else /* файл успешно открыт; копируем его в канал IPC */
{
while (fgets(mesg.mesg_data, MAXMESGDATA, fp) != NULL)
{
mesg.mesg_len = strlen(mesg.mesg_data);
if (msgsnd(writeid, &mesg, sizeof(mesg.mesg_len) +
strlen(mesg.mesg_data), 0) == -1)
{
fprintf(stderr, "Сервер: ошибка записи содержимого файла %s в канал
IPC: %s\n", ptr, strerror(errno));
exit(1);
}
}
if (ferror(fp))
{
snprintf(mesg.mesg_data, sizeof(mesg.mesg_data), "Сервер: ошибка чтения
содержимого файла %s: %s\n", ptr, strerror(errno));
mesg.mesg_len = strlen(mesg.mesg_data);
if (msgsnd(writeid, &mesg, sizeof(mesg.mesg_len) +
strlen(mesg.mesg_data), 0) == -1)
{
fprintf(stderr, "Сервер: ошибка записи в канал IPC: %s\n",
strerror(errno));
exit(1);
}
}
fclose(fp);
}
/* отправка сообщения без данных для обозначения конца связи */
mesg.mesg_len = 0;
if (msgsnd(writeid, &mesg, sizeof(mesg.mesg_len), 0) == -1)
{
fprintf(stderr, "Сервер: ошибка отправки признака конца связи по каналу
IPC: %s\n", strerror(errno));
exit(1);
}
}
}
int main(int argc, char **argv)
{
int msqid;
if ((msqid = msgget(MQ_KEY1, SVMSG_MODE | IPC_CREAT)) == -1)
{
fprintf(stderr, "Сервер: ошибка вызова функции msgget(%lu): %s\n", MQ_KEY1,
strerror(errno));
exit(1);
}
server(msqid, msqid); /* одна очередь в обе стороны */
exit(0);
}
Примечание. Определение структурыmymesg, а также список включаемых заголовочных файлов и макроподстановок, необходимых для компиляции программы-сервера, должны быть взяты из листинга 17.2.
В листинге 17.5приведен текст программы-клиента. Клиент открывает очередь сообщений, которая должна была быть создана сервером заранее. Функцияclientподготавливает и отсылает запрос серверу и обрабатывает его ответ. Эта функция представляет собой комбинацию программ из листингов 13.7 и 17.3. В первой программе клиент отсылал свой идентификатор и полное имя запрашиваемого файла, а во второй программе использовались функцииmsgsndиmsgrcv. Обратите внимание, что тип сообщений, считываемых функциейmsgrcv, совпадает со значением идентификатора процесса клиента.
Листинг 17.5. Программа-клиент последовательного сервера файлов, использующая одну очередь сообщений
void client(int readid, int writeid)
{
size_t len;
ssize_t n;
char *ptr;
struct mymesg mesg;
pid_t pid;
pid = getpid();
/* записываем в буфер идентификатор процесса и пробел */
snprintf(mesg.mesg_data, MAXMESGDATA, "%ld ", (long) pid);
len = strlen(mesg.mesg_data);
ptr = mesg.mesg_data + len;
/* чтение полного имени файла из стандартного потока ввода */
if (fgets(ptr, MAXMESGDATA - len, stdin) == NULL)
{
fprintf(stderr, "Клиент: ошибка чтения полного имени файла из стандартного
потока ввода\n");
exit(1);
} /* fgets() гарантирует завершающий нулевой байт */
len = strlen(mesg.mesg_data);
if (mesg.mesg_data[len-1] == '\n')
len--; /* удаление символа перевода строки (если есть) */
if (*ptr == '\n')
{
fprintf(stderr, "Клиент: введено пустое имя файла, завершение работы\n");
exit(1);
}
mesg.mesg_len = len;
mesg.mesg_type = 1;
/* запись запроса в канал IPC */
if (msgsnd(writeid, &mesg, sizeof(mesg.mesg_len) + len, 0) == -1)
{
fprintf(stderr, "Клиент: ошибка записи запроса в канал IPC: %s\n",
strerror(errno));
exit(1);
}
/* считывание из канала IPC, запись в стандартный поток вывода */
while ((n = msgrcv(readid, &mesg, sizeof(mesg.mesg_len) + MAXMESGDATA, pid,
0)) > 0)
{
if (n == sizeof(mesg.mesg_len))
break; /* принято сообщение без данных; конец связи */
if (write(STDOUT_FILENO, mesg.mesg_data, n - sizeof(mesg.mesg_len)) != n –
sizeof(mesg.mesg_len))
{
fprintf(stderr, "Клиент: ошибка записи содержимого файла в стандартный
поток вывода: %s\n", strerror(errno));
exit(1);
}
}
if (n == -1)
{
fprintf(stderr, "Клиент: ошибка чтения содержимого файла из канала IPC:
%s\n", strerror(errno));
exit(1);
}
}
int main(int argc, char **argv)
{
int msqid;
/* сервер должен был создать очередь */
if ((msqid = msgget(MQ_KEY1, 0)) == -1)
{
fprintf(stderr, "Клиент: ошибка вызова функции msgget(%lu): %s\n", MQ_KEY1,
strerror(errno));
exit(1);
}
client(msqid, msqid); /* одна очередь в обе стороны */
exit(0);
}
Примечание. Определение структурыmymesg, а также список включаемых заголовочных файлов и макроподстановок, необходимых для компиляции программы-клиента, должны быть взяты из листинга 17.3.
