Пример передачи сообщений: очередь с приоритетами
В этом разделе разработаем простое приложение для передачи сообщений. Его целью является реализация очереди, в которой для каждого элемента может быть задан приоритет. Серверный процесс будет выбирать элементы из очереди и обрабатывать их каким-либо образом. Например, элементы очереди могут быть именами файлов, а серверный процесс может копировать их на принтер. Этот пример аналогичен примеру использования FIFO из раздела 7.2.1.
Отправной точкой будет следующий заголовочный файл q.inc:
(* q.h - заголовок для примера очереди сообщений *)
const
QKEY:tkey=(1 shl 6) + 5{0105}; (* ключ очереди *)
QPERM=(6 shl 6) + (6 shl 3){0660}; (* права доступа *)
MAXOBN=50; (* макс. длина имени объекта *)
MAXPRIOR=10; (* максимальный приоритет *)
type
q_entry=record
mtype:longint;
mtext:array [0..MAXOBN] of char;
end;
pq_entry=^q_entry;
Определение QKEY задает значение ключа, которое будет обозначать очередь сообщений в системе. Определение QPERM устанавливает связанные с очередью права доступа. Так как код доступа равен octal(0660), то владелец очереди и члены его группы смогут выполнять чтение и запись. Как увидим позже, определения MAXOBN и MAXPRIOR будут налагать ограничения на сообщения, помещаемые в очередь. Последняя часть этого включаемого файла содержит определение структуры q_entry. Структуры этого типа будут использоваться в качестве сообщений, передаваемых и принимаемых следующими процедурами.
Первая рассматриваемая процедура называется enter, она помещает в очередь имя объекта, заканчивающееся нулевым символом, и имеет следующую форму:
{$i q.inc}
(* Процедура enter - поместить объект в очередь *)
function enter (objname:string;priority:longint):boolean;
var
len, s_qid:longint;
s_entry:q_entry; (* структура для хранения сообщений *)
begin
(* Проверка длины имени и уровня приоритета *)
len := length (objname);
if len > MAXOBN then
begin
warn ('слишком длинное имя');
enter:=false;
exit;
end;
if (priority > MAXPRIOR) or (priority < 0) then
begin
warn ('недопустимый уровень приоритета');
enter:=false;
exit;
end;
(* Инициализация очереди сообщений, если это необходимо *)
s_qid := init_queue;
if s_qid = -1 then
begin
enter:=false;
exit;
end;
(* Инициализация структуры переменной s_entry *)
s_entry.mtype := priority;
strlcopy (s_entry.mtext, @objname[1], MAXOBN);
(* Посылаем сообщение, выполнив ожидание, если это необходимо *)
if not msgsnd (s_qid, @s_entry, len, 0) then
begin
perror ('Ошибка вызова msgsnd');
enter:=false;
exit;
end
else
enter:=true;
end;
Первое действие, выполняемое процедурой
enter, заключается в проверке длины имени объекта и уровня приоритета. Обратите внимание на то, что минимальное значение переменной приоритета priority равно 1, так как нулевое значение приведет к неудачному завершению вызова msgsnd. Затем процедура enter «открывает» очередь, вызывая процедуру init_queue, реализацию которой приведем позже.
После завершения этих действий процедура формирует сообщение и пытается послать его при помощи вызова msgsnd. Здесь для хранения сообщения использована структура s_entry типа q_entry, и последний параметр вызова msgsnd равен нулю. Это означает, что система приостановит выполнение текущего процесса, если очередь заполнена (так как не задан флаг IPC_NOWAIT).
Процедура enter сообщает о возникших проблемах при помощи функции warn или библиотечной функции perror. Для простоты функция warn реализована следующим образом:
procedure warn (s:pchar);
begin
writeln(stderr, 'Предупреждение: ', s);
end;
В реальных системах функция warn должна записывать сообщения в специальный файл протокола.
Назначение функции init_queue очевидно. Она инициализирует идентификатор очереди сообщений или возвращает идентификатор очереди сообщений, который с ней уже связан.
{$i q.inc}
(* Инициализация очереди - получить идентификатор очереди *)
function init_queue:longint;
var
queue_id:longint;
begin
(* Попытка создания или открытия очереди сообщений *)
queue_id := msgget (QKEY, IPC_CREAT or QPERM);
if queue_id = -1 then
perror ('Ошибка вызова msgget');
init_queue:=queue_id;
end;
Следующая процедура, serve, используется серверным процессом для получения сообщений из очереди и противоположна процедуре
enter.
{$i q.inc}
(* Процедура serve - принимает и обрабатывает сообщение обслуживает
* объект очереди с наивысшим приоритетом
*)
function serve:integer;
var
r_qid:longint;
r_entry:q_entry;
begin
(* Инициализация очереди сообщений, если это необходимо *)
r_qid := init_queue;
if r_qid = -1 then
begin
serve:=-1;
exit;
end;
(* Получить и обработать следующее сообщение *)
while true do
begin
if not msgrcv(r_qid, @r_entry, MAXOBN, -1*MAXPRIOR, MSG_NOERROR) then
begin
perror ('Ошибка вызова msgrcv');
serve:=-1;
exit;
end
else
begin
(* Обработать имя объекта *)
proc_obj (@r_entry);
end;
end;
end;
Обратите внимание на вызов msgrcv. Так как в качестве параметра типа задано отрицательное значение (-1 * MAXPRIOR), то система вначале проверяет очередь на наличие сообщений со значением
mtype равным 1, затем равным 2 и так далее, до значения MAXPRIOR включительно. Другими словами, сообщения с наименьшим номером будут иметь наивысший приоритет. Процедура proc_obj работает с объектом. Для системы печати она может просто копировать файл на принтер.
Две следующих простых программы демонстрируют взаимодействие этих процедур: программа etest помещает элемент в очередь, а программа stest обрабатывает его (в действительности она всего лишь выводит содержимое и тип сообщения).