В этом разделе разработаем простое приложение для передачи сообщений. Его целью является реализация очереди, в которой для каждого элемента может быть задан приоритет. Серверный процесс будет выбирать элементы из очереди и обрабатывать их каким-либо образом. Например, элементы очереди могут быть именами файлов, а серверный процесс может копировать их на принтер. Этот пример аналогичен примеру использования FIFO.
Отправной точкой будет следующий заголовочный файл q.h:
/* q.h – заголовок для примера очереди сообщений */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <errno.h>
#define QKEY (key_t) 0105 /* Ключ очереди */
#define QPERM 0660 /* Права доступа */
#define MAXOBN 50 /* Макс. длина имени объекта */
#define MAXPRIOR 10 /* Максимальный приоритет */
struct q_entry
{
long mtype;
char mtext [MAXOBN+1];
};
Первая часть этого файла содержит необходимые включаемые файлы. Определение QKEY задает значение ключа, которое будет обозначать очередь сообщений в системе. Определение QPERM устанавливает связанные с очередью права доступа. Так как код доступа равен 0660, то владелец очереди и члены его группы смогут выполнять чтение и запись. Как увидим позже, определения MAXOBN и MAXPRIOR будут налагать ограничения на сообщения, помещаемые в очередь. Последняя часть этого включаемого файла содержит определение структуры q_entry. Структуры этого типа будут использоваться в качестве сообщений, передаваемых и принимаемых следующими процедурами.
Первая рассматриваемая процедура называется enter, она помещает в очередь имя объекта, заканчивающееся нулевым символом, и имеет следующую форму:
/* Процедура enter – поместить объект в очередь */
#include “q.h”
int enter (char *objname, int priority)
{
int len, s_qid;
struct q_entry s_entry; /* Структура для хранения сообщений */
/* Проверка длины имени и уровня приоритета */
if((len = strlen(objname)) > MAXOBN)
{
warn (“ Слишком длинное имя “);
return (-1);
}
if (priority > MAXPRIOR || priority <0)
{
warn (“ Недопустимый уровень приоритета ”);
return (-1);
}
/* Инициализация очереди сообщений, если это необходимо */
if((s_qid = init_queue()) == -1)
return (-1);
/* Инициализация структуры s_entry */
s_entry.mtype = (long)priority;
strcpy (s_entry.mtext, objname, MAXOBN);
/* Посылаем сообщение, выполнив ожидание, если это необходимо*/
if (msgsnd(s_qid, &s_entry, len, 0) == -1)
{
perror(“Ошибка вызова msgsnd”);
return (-1);
}
else
return (0);
}
Первое действие, выполняемое процедурой enter, заключается в проверке длины имени объекта и уровня приоритета. Обратите внимание на то, что минимальное значение переменной приоритета priority равно 1, так как нулевое значение приведет к неудачному завершению вызова msgsnd. Затем процедура enter «открывает» очередь, вызывая процедуру init_gueue, реализацию которой приведем позже.
После завершения этих действий процедура формирует сообщение и пытается послать его при помощи вызова msgsnd. Здесь для хранения сообщения использована структура s_entry типа q_entry, и последний параметр вызова msgsnd равен нулю. Это означает, что система приостановит выполнение текущего процесса, если очередь заполнена (так как не задан флаг IPC_NOWAIT ).
Процедура enter сообщает о возникших проблемах при помощи функции warn или библиотечной функции perror. Для простоты функция warn реализована следующим образом:
#include <stdio.h>
int warn(char *s)
{
fprintf (stderr, "Предупреждение: %s\n", s);
}
В реальных системах функция warn должна записывать сообщения в специальный файл протокола.
Назначение функции init_queue очевидно. Она инициализирует идентификатор очереди сообщений или возвращает идентификатор очереди сообщений, который с ней уже связан.
/* Инициализация очереди — получить идентификатор очереди */
#include "q.h"
int init_queue(void)
{
int queue_id;
/* Попытка создания или открытия очереди сообщений */
if ((queue_id = msgget(QKEY, IPC_CREAT | QPERM)) == -1)
perror(“ Ошибка вызова msgget”);
return (queue_id);
}
Следующая процедура, serve, используется серверным процессом для получения сообщений из очереди и противоположна процедуре enter.
/* Процедура serve — принимает и обрабатывает сообщение очереди с наивысшим приоритетом */
#include "q.h"
int serve(void)
{
int mlen, r_qid;
struct q_entry r_entry;
/* Инициализация очереди сообщений, если это необходимо */
if((r_qid = init_queue()) == -1)
return (-1);
/* Получить и обработать следующее сообщение */
for(;;)
{
if((mlen = msgrcv(r_qid, &r_entry, MAXOBN,
(-1 * MAXPRIOR), MSG_NOERROR)) == -1)
{
perror("Ошибка вызова msgrcv");
return (-1);
}
else
{
/* Убедиться, что это строка */
r_entry.mtext[mlen]='\0';
/* Обработать имя объекта */
proc_obj(&r_entry);}
}
}
Обратите внимание на вызов msgrcv. Так как в качестве параметра типа задано отрицательное значение (-1 * MAXPRIOR), то система вначале проверяет очередь на наличие сообщений со значением mtype равным 1, затем равным 2 и так далее, до значения MAXPRIOR включительно. Другими словами, сообщения с наименьшим номером будут иметь наивысший приоритет. Процедура proc_obj работает с объектом. Для системы печати она может просто копировать файл на принтер.
Две следующих простых программы демонстрируют взаимодействие этих процедур: программа etest помещает элемент в очередь, а программа stest обрабатывает его (в действительности она всего лишь выводит содержимое и тип сообщения).
Программа etest
/* Программа etest - ввод имен объектов в очередь */
#include <stdio.h>
#include <stdlib.h>
#include "q.h"
main(int argc, char **argv)
{
int priority;
if(argc!= 3)
{
fprintf(stderr, "Применение: %s имя приоритет\n, argv[0]);
exit(1);
}
if((priority = atoi(argv[2])) <= 0 || priority > MAXPRIOR)
{
warn("Недопустимый приоритет");
exit(2);
}
if(enter(argv[1], priority) < 0)
{
warn("Ошибка в процедуре enter");
exit(3);
}
exit(0);
}
Программа stest
/* Программа stest - простой сервер для очереди */
#include <stdio.h>
#include "q.h"
main()
{
pid_t pid;
switch(pid = fork())
{
case 0: /* Дочерний процесс */
serve();
break; /* Сервер не существует */
case -1:
warn(“Не удалось запустить сервер”);
break;
default:
printf(“Серверный процесс с идентификатором %d\n”, pid);
}
exit(pid!= -1? 0: 1);
}
int proc_obj(struct q_entry *msg)
{
printf(“\nПриоритет: %ld имя: %s\n”, msg->mtype, msg->mtext);
}
Ниже следует пример использования этих двух простых программ. Перед запуском программы stest в очередь вводятся четыре простых сообщения при помощи программы etest. Обратите внимание на порядок, в котором выводятся сообщения:
$ etest objname1 3
$ etest objname2 4
$ etest objname3 1
$ etest objname4 9
$ etest
Серверный процесс с идентификатором 2545
$
Приоритет 1 имя objname3
Приоритет 3 имя objname1
Приоритет 4 имя objname2
Приоритет 9 имя objname4