Уильям Стивенс - UNIX: взаимодействие процессов
//svmsgmpxnq/server.c
1 #include "mesg.h"
2 void
3 server(int readid, int writeid)
4 {
5 FILE *fp;
6 char *ptr;
7 ssize_t n;
8 struct mymesg mesg;
9 void sig_chld(int);
10 Signal(SIGCHLD, sig_chld);
11 for (;;) {
12 /* считывание имени файла из очереди */
13 mesg.mesg_type = 1;
14 if ((n = Mesg_recv(readid, &mesg)) == 0) {
15 err_msg("pathname missing");
16 continue;
17 }
18 mesg.mesg_data[n] = 40'; /* имя файла */
19 if ((ptr = strchr(mesg.mesg_data, ' ')) = NULL) {
20 err_msg("bogus request: %s", mesg.mesg_data);
21 continue;
22 }
23 *ptr++ = 0; /* ptr = имя файла */
24 writeid = atoi(mesg.mesg_data);
25 if (Fork() == 0) { /* дочерний процесс */
26 if ((fp = fopen(ptr, "r")) == NULL) {
27 /* ошибка: нужно сообщить клиенту */
28 snprintf(mesg.mesg_data + n, sizeof(mesg.mesg_data) – n,
29 ": can't open, %sn", strerror(errno));
30 mesg.mesg_len = strlen(ptr);
31 memmove(mesg.mesg_data, ptr, mesg.mesg_len);
32 Mesg_send(writeid, &mesg);
33 } else {
34 /* файл открыт, копируем клиенту */
35 while (Fgets(mesg.mesg_data, MAXMESGDATA, fp) != NULL) {
36 mesg.mesg_len = strlen(mesg.mesg_data);
37 Mesg_send(writeid, &mesg);
38 }
39 Fclose(fp);
40 }
41 /* отправка сообщения нулевой длины, указывающего конец файла */
42 mesg.mesg_len = 0;
43 Mesg_send(writeid, &mesg);
44 exit(0); /* завершение дочернего процесса */
45 }
46 /* родительский процесс просто зациклен */
47 }
48 }
Листинг 6.20. Функция-обертка Mesg_recv, обрабатывающая прерванный системный вызов//svmsgmpxnq/mesg_recv.с
10 ssize_t
11 Mesg_recv(int id, struct mymesg *mptr)
12 {
13 ssize_t n;
14 do {
15 n = mesg_recv(id, mptr);
16 } while (n == –1 && errno == EINTR);
17 if (n == –1)
18 err_sys("mesg_recv error");
19 return(n);
20 }
6.9. Использование select и poll с очередями сообщений
Одним из недостатков очередей сообщений System V является то, что они идентифицируются не дескрипторами, а идентификаторами. Поэтому с ними нельзя использовать функции select и poll (глава 6 [24]).
ПРИМЕЧАНИЕ
На самом деле одна из версий Unix, а именно AIX (созданная IBM), позволяет использовать select с очередями сообщений System V, а не только с дескрипторами. Но эта возможность имеется только в AIX.
Этот недостаток часто всплывает, когда возникает необходимость написать сервер, работающий одновременно с сетевыми соединениями и с IPC. Сетевые соединения с использованием интерфейса сокетов или XTI ([24]) используют дескрипторы, что позволяет вызывать select или poll. Программные каналы и FIFO также идентифицируются дескрипторами, поэтому для них тоже допустимо использование этих функций.
Одним из решений этой проблемы является следующее: сервер должен создать канал и породить процесс, который будет заблокирован при вызове msgrcv. При получении сообщения произойдет возврат из msgrcv, дочерний процесс получит это сообщение из очереди и запишет его в канал. Затем родительский процесс может использовать функцию select для канала совместно с сетевыми соединениями. Недостаток этого подхода в том, что сообщения обрабатываются трижды: при считывании дочерним процессом с помощью msgrcv, при отправке в канал и при считывании из канала родительским процессом. Для ускорения обработки порожденный процесс может создать сегмент совместно используемой с породившим процессом памяти, а канал использовать как флаг (упражнение 12.5).
ПРИМЕЧАНИЕ
В листинге 5.12 мы привели решение с использованием очередей сообщений Posix, которое не требовало вызова fork. Для очередей сообщений Posix можно было обойтись одним процессом, поскольку они предусматривают уведомление о появлении нового сообщения с помощью сигнала. Для очередей System V такая возможность не предусмотрена, поэтому приходится порождать процесс, который будет блокироваться при вызове msgrcv.
Другим недостатком очередей сообщений System V по сравнению с сетевым интерфейсом является невозможность считывания сообщений из оперативной памяти (возможность, предоставляемая флагом MSG_PEEK для функций recv, recvfrom, recvmsg [24, с. 356]). Если бы такая возможность имелась, в предложенной только что схеме клиент-сервер (для обхода проблемы с select) можно было бы сделать работу более эффективной, указав флаг peek при вызове msgrcv дочерним процессом и записав 1 байт в канал при приходе сообщения, а родительский процесс тогда просто считывал бы сообщение из очереди.
6.10. Ограничения, накладываемые на очереди сообщений
Как отмечалось в разделе 3.8, на очереди сообщений часто накладываются системные oгрaничeния. В табл. 6.2 приведены значения этих oгрaничeний для двух конкретных реализаций. Первая колонка представляет собой традиционное имя System V для переменной ядра, хранящей это ограничение.
Таблица 6.2. Характерные значения ограничений для очередей сообщений
Имя Описание DUnix 4.0B Solaris 2.6 msgmax Максимальное количество байтов в сообщении 8192 2048 msgmnb Максимальное количество байтов в очереди сообщений 16384 4096 msgmni Максимальное количество очередей сообщений в системе 64 50 msgtlq Максимальное количество сообщений в системе 40 40В этом разделе мы хотели показать типичные значения ограничений, чтобы помочь в планировании переносимых программ. При выполнении приложений, активно использующих очереди сообщений, обычно требуется настройка этих (или аналогичных) параметров ядра (что описано в разделе 3.8).
Пример
В листинге 6.21 приведен текст программы, которая определяет четыре ограничения, показанные в табл. 6.2.
Листинг 6.21. Определение системных ограничений для очередей сообщений System V//svmsg/limits.c
1 #include "unpipc.h"
2 #define MAX_DATA 64*1024
3 #define MAX_NMESG 4096
4 #define MAX_NIDS 4096
5 int max_mesg;
6 struct mymesg {
7 long type;
8 char data[MAX_DATA];
9 } mesg;
10 int
11 main(int argc, char **argv)
12 {
13 int i, j, msqid, qid[MAX_NIDS];
14 /* определение максимального размера сообщения */
15 msqid = Msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);
16 mesg.type = 1;
17 for (i = MAX_DATA; i > 0; i –= 128) {
18 if (msgsnd(msqid, &mesg, i, 0) == 0) {
19 printf("maximum amount of data per message = %dn", i);
20 max_mesg = i;
21 break;
22 }
23 if (errno != EINVAL)
24 err_sys("msgsnd error for length %d", i);
25 }
26 if (i == 0)
27 err_quit("i == 0");
28 Msgct(lmsqid, IPC_RMID, NULL);
29 /* количество сообщений в очереди */
30 mesg.type = 1;
31 for (i = 8; i <= max_mesg; i *= 2) {
32 msqid = Msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);
33 for (j = 0; j < MAX_NMESG; j++) {
34 if (msgsnd(msqid, &mesg, i, IPC_NOWAIT) != 0) {
35 if (errno == EAGAIN)
36 break;
37 err_sys("msgsnd error, i = %d, j = %d", i, j);
38 break;
39 }
40 }
41 printf("%d %d-byte messages were placed onto queue,", j, i);
42 printf(" %d bytes totaln". i*j);
43 Msgctl(msqid, IPC_RMID, NULL);
44 }
45 /* максимальное количество идентификаторов */
46 mesg.type = 1;
47 for (i = 0; i <= MAX_NIDS; i++) {
48 if ((qid[i] = msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT)) == –1) {
49 printf("%d identifiers open at oncen", i);
50 break;
51 }
52 }
53 for (j = 0; j < i; j++)
54 Msgctl(qid[j], IPC_RMID, NULL);
55 exit(0);
56 }
Определение максимального размера сообщения14-28 Для определения максимально возможного размера сообщения мы пытаемся послать сообщение, в котором будет 65 536 байт данных, и если эта попытка оказывается неудачной, уменьшаем этот объем до 65 408, и т.д., пока вызов msgsnd не окажется успешным.