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

18.7.2. Пример: одна очередь для каждого клиента

Изменим теперь предыдущий пример таким образом, чтобы все запросы клиентов передавались по одной очереди, но для отправки ответов использовалась бы отдельная очередь для каждого клиента. На рис. 17.3 изображена схема такого приложения.

рис. 17.3

Ключ очереди сервера должен быть известен клиентам, а сами клиенты создают свои очереди с ключом IPC_PRIVATE. Вместо передачи серверу идентификатора процесса клиенты сообщают ему идентификатор своей очереди, в которую сервер направляет свой ответ. Этот сервер является параллельным: для обслуживания каждого нового клиента порождается отдельный процесс.

При такой схеме может возникнуть проблема в случае аварийного завершения программы-клиента, потому что его персональная очередь останется в системе навсегда (по крайней мере, до перезагрузки ядра или явного удаления очереди другим процессом).

В листинге 17.6приведен текст программы-клиента; он слегка изменился по сравнению с листингом 17.5. Мы открываем очередь сервера с известным ключом (MQ_KEY1) и создаем нашу собственную очередь с ключомIPC_PRIVATE. Два идентификатора этих очередей являются аргументами функцииclient. После завершения работы клиента его персональная очередь удаляется. Функцияclientпрактически идентична одноименной функции из листинга 17.5, но вместо передачи идентификатора процесса клиента на сервер передается идентификатор персональной очереди клиента. Тип сообщения в структуре типаmymesgостается равным 1, поскольку это значение устанавливается для сообщений, передаваемых в обе стороны.

Листинг 17.6. Программа-клиент параллельного сервера файлов, использующая персональную очередь сообщений для приема файла

#include <sys/stat.h>

#define MSG_R S_IRUSR

#define MSG_W S_IWUSR

#define SVMSG_MODE (MSG_R | MSG_W | MSG_R>>3 | MSG_R>>6)

int readid, writeid;

void msg_del(void)

{

/* удаление очереди сообщений */

if (msgctl(readid, IPC_RMID, NULL) == -1)

fprintf(stderr, "Клиент: ошибка вызова функции msgctl(%lu, IPC_RMID): %s\n",

IPC_PRIVATE, strerror(errno));

}

void client(int readid, int writeid)

{

size_t len;

ssize_t n;

char *ptr;

struct mymesg mesg;

/* записываем в буфер идентификатор очереди и пробел */

snprintf(mesg.mesg_data, MAXMESGDATA, "%d ", readid);

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, 1, 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)

{

if (atexit(msg_del))

{

fprintf(stderr, "Клиент: невозможно зарегистрировать msg_del: %s\n",

strerror(errno));

exit(1);

}

/* сервер должен был создать свою очередь */

if ((writeid = msgget(MQ_KEY1, 0)) == -1)

{

fprintf(stderr, "Клиент: ошибка вызова функции msgget(%lu): %s\n", MQ_KEY1,

strerror(errno));

exit(1);

} /* мы создаем свою собственную очередь */

if ((readid = msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT)) == -1)

{

fprintf(stderr, "Клиент: ошибка вызова функции msgget(%lu): %s\n",

IPC_PRIVATE, strerror(errno));

exit(1);

}

client(readid, writeid);

exit(0);

}

Примечание. Определение структурыmymesg, а также список дополнительно включаемых заголовочных файлов и макроподстановок, необходимых для компиляции программы-клиента, должны быть взяты из листинга 17.3.

В листинге 17.7приведен текст программы-сервера. Ее главное отличие от программы из листинга 17.4 состоит в том, что внутри функцииserverзапускается бесконечный цикл, в котором для обслуживания каждого нового клиента с помощью функцииforkпорождается дочерний процесс.

Листинг 17.7. Параллельный сервер файлов, порождающий дочерний процесс для обслуживания каждого клиента

#include <signal.h>

#include <sys/wait.h>

void sig_chld(int signo)

{

int stat;

while (waitpid(-1, &stat, WNOHANG) > 0);

return;

}

void server(int readid)

{

FILE *fp;

char *ptr;

ssize_t n;

struct mymesg mesg;

char buff[MAXMESGDATA];

int writeid;

pid_t pid;

if (signal(SIGCHLD, sig_chld) == SIG_ERR)

{

fprintf(stderr, "Невозможно перехватить сигнал SIGCHLD: %s\n",

strerror(errno));

exit(1);

}

for ( ; ; )

{ /* чтение полного имени файла из канала IPC */

do

{

n = msgrcv(readid, &mesg, sizeof(mesg.mesg_len) + MAXMESGDATA, 1, 0);

} while (n == -1 && errno == EINTR);

if (n == -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 после увеличения (++) указывает на имя запрошенного файла */

writeid = atoi(buff); /* для обратных сообщений клиенту */

if ((pid = fork()) == -1)

{

fprintf(stderr, "Сервер: ошибка вызова функции fork: %s\n",

strerror(errno));

exit(1);

}

if (pid == 0)

{ /* дочерний процесс */

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);

}

exit(0); /* завершение дочернего процесса */

}

/* родительский процесс просто зациклен */

}

}

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); /* очередь для считывания запросов клиентов */

exit(0);

}

Примечание. Определение структурыmymesg, а также список дополнительно включаемых заголовочных файлов и макроподстановок, необходимых для компиляции программы-сервера, должны быть взяты из листинга 17.2.

Вызов функции msgrcvблокирует сервер до прихода сообщения от очередного клиента. Вызов функцииforkпорождает дочерний процесс, который пытается открыть запрошенный файл и отправляет клиенту либо сообщение об ошибке, либо содержимое файла. Мы преднамеренно поместили вызов функцииfopenв дочерний процесс, а не в родительский, поскольку если файл находится в удаленной файловой системе, его открытие может занять довольно много времени в случае наличия проблем с сетью.

Обработка сигнала SIGCHLD. Поскольку для обслуживания каждого клиента порождается отдельный процесс, мы должны позаботиться о процессах-зомби (см. разделы 8.5 и 8.6). Здесь мы просто устанавливаем обработчик для сигналаSIGCHLD, и наша функцияsig_chldбудет вызываться при завершении работы любого дочернего процесса.

Каждый раз при вызове обработчика внутри цикла вызывается функция waitpid, которая получает код завершения всех дочерних процессов, которые могли завершить работу. Затем происходит возврат из обработчика сигнала в основную программу (в функциюserver). При этом может возникнуть проблема, поскольку родительский процесс проводит большую часть времени в заблокированном состоянии, ожидая прихода сообщения от очередного клиента, предварительно вызвав функциюmsgrcv. При возврате из обработчика сигнала этот вызов функцииmsgrcvпрерывается, причем функция возвращает ошибку с кодомEINTR.

Нам нужно обработать такой возврат из вызванной функции, поэтому функция msgrcvвызывается внутри циклаdo {...} while. Если происходит ошибка с кодомEINTR, то функцияmsgrcvпросто вызывается повторно.

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