Уильям Стивенс - UNIX: взаимодействие процессов
//svmsgcliserv/svmsg.h
1 #include "unpipc.h"
2 #define MQ_KEY1 1234L
3 #define MQ_KEY2 2345L
Функция main для сервера приведена в листинге 6.8. Программа создает обе очереди сообщений, и не беда, если какая-нибудь из них уже существует, потому что мы не указываем флаг IPC_EXCL. Функция server дана в листинге 4.16. Она вызывает наши собственные функции mesgsend и mesgrecv, новые версии которых будут приведены ниже.
Листинг 6.8. Функция main программы-сервера, использующей очереди сообщений//svmsgcliserv/server_main.с
1 #include "svmsg.h"
2 void server(int, int);
3 int
4 main(int argc, char **argv)
5 {
6 int readid, writeid;
7 readid = Msgget(MQ_KEY1, SVMSG_MODE | IPC_CREAT);
8 writeid = Msgget(MQ_KEY2, SVMSG_MODE | IPC_CREAT);
9 server(readid, writeid);
10 exit(0);
11 }
Листинг 6.9. Функция main программы-клиента, использующей очереди сообщений//svmsgcliserv/client_main.с
1 #include "svmsg.h"
2 void client(int, int);
3 int
4 main(int argc, char **argv)
5 {
6 int readid, writeid;
7 /* assumes server has created the queues */
8 writeid = Msgget(MQ_KEY1, 0);
9 readid = Msgget(MQ_KEY2, 0);
10 client(readid, writeid);
11 /* now we can delete the queues */
12 Msgctl(readid, IPC_RMID. NULL);
13 Msgctl(writeid, IPC_RMID, NULL);
14 exit(0);
15 }
В листинге 6.9 приведен текст функции main программы-клиента. Программа открывает две очереди сообщений и вызывает функцию client из листинга 4.15. Эта функция использует две другие: mesg_send и mesg_recv, которые будут приведены ниже.
И функция client, и функция server используют формат сообщений, изображенный в листинге 4.12. Для передачи и приема сообщений они используют функции mesg_send и mesg_recv. Старые версии этих функций, приведенные в листингах 4.13 и 4.14, вызывали write и read и работали с программными каналами и FIFO, так что нам придется переписать их для использования очередей сообщений. В листингах 6.10 и 6.11 приведены новые версии этих функций. Обратите внимание, что аргументы функций не изменились, поскольку первый целочисленный аргумент может содержать как целочисленный дескриптор программного канала или FIFO, так и целочисленный дескриптор очереди сообщений.
Листинг 6.10. Функция mesg_send, работающая с очередью сообщений System V//svmsgcliserv/mesg_send.с
1 #include "mesg.h"
2 ssize_t
3 mesg_send(int id, struct mymesg *mptr)
4 {
5 return(msgsnd(id, &(mptr->mesg_type), mptr->mesg_len, 0));
6 }
Листинг 6.11. Функция mesg_recv, работающая с очередью сообщений System V//svmsgcliserv/mesg_recv.с
1 #include "mesg.h"
2 ssize_t
3 mesg_recv(int id, struct mymesg *mptr)
4 {
5 ssize_t n;
6 n = msgrcv(id, &(mptr->mesg_type), MAXMESGDATA, mptr->mesg_type, 0);
7 mptr->mesg_len = n; /* количество возвращаемых данных */
8 return(n); /* –1 в случае ошибки, 0 – конец файла, иначе – >0 */
9 }
6.8. Мультиплексирование сообщений
Наличие поля type у каждого сообщения в очереди предоставляет две интересные возможности:
1. Поле type может использоваться для идентификации сообщений, позволяя нескольким процессам мультиплексировать сообщения в одной очереди. Например, все сообщения от клиентов серверу имеют одно и то же значение типа, тогда как сообщения сервера клиентам имеют различные значения типов, уникальные для каждого клиента. Естественно, в качестве значения типа сообщения, гарантированно уникального для каждого клиента, можно использовать идентификатор процесса клиента.
2. Поле type может использоваться для установки приоритета сообщений. Это позволяет получателю считывать сообщения в порядке, отличном от обычного для очередей (FIFO). В программных каналах и FIFO данные могли приниматься только в том порядке, в котором они были отправлены. Очереди System V позволяют считывать сообщения в произвольном порядке в зависимости от значений типа сообщений. Более того, можно вызывать msgrcv с флагом IPC_NOWAIT для считывания сообщений с конкретным типом и немедленного возвращения управления процессу в случае отсутствия таких сообщений.
Пример: одна очередь на приложение
Вспомните наш простой пример с одним процессом-сервером и одним процессом-клиентом. Если применять программные каналы или FIFO, необходимо наличие двух каналов IPC для передачи данных в обоих направлениях, поскольку эти типы IPC являются однонаправленными. Очереди сообщений позволяют передавать данные в обоих направлениях, причем поле type может использоваться для указания адресата (клиента или сервера).
Рис. 6.2. Мультиплексирование сообщений между несколькими клиентами и одним сервером
Рассмотрим усложненный вариант: один сервер и несколько клиентов. В этом случае можно использовать значение типа 1, например, для обозначения сообщений от любого клиента серверу. Если клиент передаст серверу свой идентификатор процесса в качестве части сообщения, сервер сможет отсылать клиенту сообщения, используя его идентификатор в качестве значения типа сообщения. Каждый клиент будет использовать свой PID в качестве аргумента type при вызове msgrcv. На рис. 6.2 приведен пример использования очереди для мультиплексирования этих сообщений между несколькими клиентами и одним сервером.
ПРИМЕЧАНИЕ
При использовании одного канала IPC одновременно клиентами и сервером всегда существует потенциальная возможность зависания (deadlock). Клиенты могут (в этом примере) заполнить очередь своими сообщениями, не давая серверу возможности отправить ответ. В этому случае клиенты заблокируются при вызове msgsnd, как и сервер. Одно из соглашений, исключающих возможность такой взаимной блокировки, заключается в том, что сервер должен всегда отключать блокировку записи в очередь сообщений.
Теперь мы можем переделать наш пример с клиентом и сервером, используя одну очередь сообщений с различными типами для разных адресатов. Эти программы используют следующее соглашение: сообщения с типом 1 адресованы серверу, а все остальные сообщения имеют тип, соответствующий идентификатору процесса адресата. При этом запрос клиента должен содержать его PID вместе с полным именем запрашиваемого файла, аналогично программе в разделе 4.8.
В листинге 6.12 приведен текст функции main сервера. Заголовочный файл svmsg.h был приведен в листинге 6.7. Создается единственная очередь сообщений (если она существует, ошибки не возникнет). Идентификатор этой очереди сообщений используется в качестве обоих аргументов при вызове функции server.
Листинг 6.12. Функция main сервера//svmsgmpx1q/server_main.с
1 #include "svmsg.h"
2 void server(int, int);
3 int
4 main(int argc, char **argv)
5 {
6 int msqid;
7 msqid = Msgget(MQ_KEY1, SVMSG_MODE | IPC_CREAT);
8 server(msqid, msqid); /* одна очередь в обе стороны */
9 exit(0);
10 }
Функция server обеспечивает работу сервера. Ее текст приведен в листинге 6.13. Эта функция представляет собой комбинацию листинга 4.10 — нашего сервера FIFO, считывавшего команды, состоявшие из идентификатора процесса и полного имени файла, — и листинга 4.16, в котором использовались функции mesg_send и mesg_recv. Обратите внимание, что идентификатор процесса, отправляемый клиентом, используется в качестве типа для всех сообщений, отправляемых сервером этому клиенту. Эта функция представляет собой бесконечный цикл, в котором считываются запросы клиентов и отсылаются запрошенные файлы. Этот сервер является последовательным (см. раздел 4.9).
В листинге 6.14 приведен текст функции main клиента. Клиент открывает очередь сообщений, которая должна была быть создана сервером заранее.
Функция client, текст которой дан в листинге 6.15, обеспечивает всю обработку со стороны клиента. Эта функция представляет собой комбинацию программ из листингов 4.11 и 4.15. В первой программе клиент отсылал свой идентификатор и полное имя файла, а во второй программе использовались функции mesg_send и mesg_recv. Обратите внимание, что тип сообщений, запрашиваемых функцией mesg_recv, совпадает с идентификатором процесса клиента.
Функции client и server используют функции mesg_send и mesg_recv из листингов 6.9 и 6.11.
Листинг 6.13. Функция server//svmsgmpx1q/server.c
1 #include "mesg.h"
2 void
3 server(int readfd, int writefd)
4 {
5 FILE *fp;
6 char *ptr;
7 pid_t pid;
8 ssize_t n;
9 struct mymesg mesg;
10 for (;;) {
11 /* считывание полного имени из канала IPC */