возможность проанализировать многие сравнительные показатели выбираемого инструментария: простота, гибкость, эффективность, трудоемкость реализации, возможности внесения изменений при развитии проекта и на этапе его последующего сопровождения. Этим мы и займемся в оставшейся части главы.
Простота и трудоемкость
Механизм прямого обмена сообщениями крайне просто выражается в программном коде. Когда достигнута полная ясность в значениях адресных параметров обмена, необходимо всего лишь несколько операторов, чтобы заставить все это «крутиться».
Со стороны сервера, например, это выглядит так:
int chid = ChannelCreate(0);
...
while (true) {
struct _msg_info info;
int rcvid = MsgReceive(chid, &bufin, sizeof(bufin), &info);
if (rcvid < 0) exit(EXIT_FAILURE);
if (MsgReply(rcvid, EOK, &bufou, sizeof(bufou) < 0) exit(EXIT_FAILURE);
}
Co стороны клиента:
int coid = ConnectAttach(node, pid, chid, _NTO_SIDE_CHANNEL, 0);
if (coid < 0) exit(EXIT_FAILURE);
...
while(...)
if (MsgSend(coid, &bufou, sizeof(bufou), &bufin, sizeof(bufin)) == -1)
exit(EXIT_FAILURE);
}
Код для реализации того же обмена, но организованного как менеджер ресурса, будет как минимум в несколько раз объемнее (образцы менеджеров мы уже видели ранее по тексту). Кроме того, по большей части он будет состоять из заполнения полей некоторых внутренних структур, используемых библиотеками менеджера ресурсов или пула потоков. На первый поверхностный взгляд такой код маловразумителен.
С другой стороны, весь достаточно объемный код любого менеджера ресурса — это очередное повторение одного и того же общего шаблона для написания менеджеров. При некоторых минимальных навыках написание самых замысловатых менеджеров ресурсов становится совершенно рутинным занятием, не превышающим по трудоемкости написание простого обмена сообщениями. Большим подспорьем здесь является наличие в комплекте технической документации QNX огромного (более 80 страниц) раздела, исчерпывающе описывающего технику создания менеджеров ресурсов; по качеству и скрупулезности изложения это одна из лучших частей всей технической документации.
Гибкость и мобильность
При установлении соединения техника простого обмена сообщениями в качестве адресата сообщений (сервера) использует «магическую тройку» (триплет [1]) параметров ND, PID и CHID, где:
• ND
— дескриптор сетевого узла, на котором работает интересующая нас программа-сервер (узел, на который надо отсылать сообщение);
• PID
— PID процесса этой программы на своем сетевом узле (кому отсылать сообщение);
• CHID
— номер канала, который открыла эта программа для приема сообщений данного вида.
В этой адресации, пожалуй, и кроется самая главная причина негибкости механизма обмена сообщениями. Дескриптор сетевого узла nd
, значение которого, кроме того, способно самопроизвольно изменяться в сети с течением времени, мы можем установить по сетевому имени интересующего нас хоста, используя netmgr_strtond()
. (Это действие по своей сути избыточное, дополнительный уровень косвенности, так как первичным идентификатором узла для пользователя приложения является его имя, а не дескриптор.)
Гораздо хуже дело обстоит с pid
и chid
, особенно для процесса, выполняющегося на удаленном сетевом узле. Не существует в общем виде прямого способа установить PID удаленного процесса, а тем более номер канала, который открыл этот процесс для обмена (или вообще не открывал, если мы, например, ошиблись в определении его PID). И тогда на помощь приходят некоторые искусственные приемы, построенные либо на использовании некоторых иерархических (родительский-дочерний) соотношений процессов клиента и сервера, либо на системах совершенно условных договоренностей (произвольных и варьирующихся от случая к случаю).
Р. Кертен [1] отмечает, что существует множество способов нахождения этой адресной триады, и перечисляет некоторые из них:
1. Открыть файл с известным именем и сохранить в нем ND/PID/CHID…
2. Использовать для объявления идентификаторов ND/PID/CHID глобальные переменные программы…
3. Занять часть пространства имен путей и стать администратором ресурсов.
Не вдаваясь в подробный анализ (вы это можете сделать сами), отметим, что 1-й способ — крайне искусственный и негибкий (особенно в сетевой среде), 2-й — крайне ограничен и применим лишь к узкому кругу задач, а 3-й способ подводит нас к применению совсем другой, альтернативной технологии с используемыми ею принципами адресации.
Несколько, безусловно, интересных и заслуживающих внимания вариаций на тему техники обмена сообщениями предлагает В. Зайцев в приложении, которое следует за данной главой.
Пользуясь случаем, внесем и мы свою лепту в «копилку» способов вычисления адресной триады и увязывания клиента с соответствующим сервером. В тех нечастых случаях, когда требуется обеспечить максимально возможную плотность потока обмена (об этом см. далее), а информационный канал желательно создать на базе именно прямого обмена сообщениями, мы предлагаем оформлять серверный процесс одновременно и как специальный менеджер ресурса, и как канал прямого обмена сообщениями. При этом клиент, пользуясь адресацией пути к менеджеру, запрашивает его по read()
или devctl()
, на которые менеджер возвращает свой PID и открытый для прямого обмена дополнительный CHID. На этом функции менеджера заканчиваются, а весь информационный обмен далее идет обменом сообщений через указанный канал. Полный текст такого сервера будет показан в примере позже.
Теперь обратимся к технологии менеджера ресурсов. В этой технике менеджер регистрирует в пространстве имен (в файловой системе) уникальное имя, по которому клиенты, заинтересованные в его ресурсе, будут адресоваться к менеджеру. Идея не нова для мира UNIX (каталоги файловой системы /proc
или /dev
, как правило, вообще не содержат реальных файлов), и она находит все более последовательное расширение в новых разработках операционных систем, отталкивающихся от UNIX, например Plan9 или Inferno.
Техника менеджера ресурса вводит дополнительный уровень разрешения имен, который реализуется через менеджер процессов procnto
(как это происходит, подробно и на примерах описывается в [1]). Происходящее при выполнении POSIX-оператора:
int fd = open('/net/host/dev/srv', O_RDONLY);