Скачиваний:
90
Добавлен:
12.05.2015
Размер:
913.92 Кб
Скачать

18.7. Мультиплексирование сообщений

Наличие поля typeу каждого сообщения в очереди предоставляет две интересные возможности:

  1. Поле typeможет использоваться для идентификации сообщений, позволяя нескольким процессам мультиплексировать сообщения в одной очереди. Например, все сообщения от клиентов серверу имеют одно и то же значение типа, тогда как сообщения сервера клиентам имеют различные значения типов, уникальные для каждого клиента. Естественно, в качестве значения типа сообщения, гарантированно уникального для каждого клиента, можно использовать идентификатор процесса клиента.

  2. Поле 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.

Соседние файлы в папке Chapter.4