При том, что это работает в большинстве случаев, вы можете столкнуться и с ситуацией, когда вам придется нарушить иерархию обмена. Это никогда не следует выполнять простым нарушением иерархии, направляя сообщения «против течения» — для этого существует функция MsgDeliverEvent() , о которой речь несколько позже.
Идентификаторы отправителя, каналы и другие параметры
Мы с вами пока не обсуждали различные параметры, используемые в ранее рассмотренных примерах, чтобы можно было сконцентрировать внимание на самих принципах обмена сообщениями. Теперь поговорим об этих параметрах более подробно.
Дополнительно о каналах
В приведенном выше примере с сервером мы видели, что сервер создал один-единственный канал. Конечно, можно было создать больше, но обычно серверы так не делают. (Наиболее очевидный пример сервера с двумя каналами — это администратор штатной сети qnet
— вот уж определенно эксцентричный образец программного обеспечения !)
Оказывается, что в действительности нет большой необходимости в создании нескольких каналов. Главное назначение канала состоит в том, чтобы четко указать серверу, где «слушать» на предмет входящих сообщений, и четко указать клиентам, куда передавать сообщения (через соответствующие соединения). Единственная ситуация, когда вам могло бы понадобиться использовать несколько каналов в сервере, — это если бы хотели реализовать сервер, предоставляющий различные услуги (или различные классы услуг) в зависимости от того, по какому каналу было принято сообщение. Второй канал мог бы применяться, например, для отправки сообщений типа «импульс», пробуждающих субсерверы — это гарантировало бы развязку этого сервиса от служебных функций, предоставляемых обычными сообщениями по первому каналу.
В предыдущем параграфе я утверждал, что вы могли бы использовать в сервере пул потоков, готовый принимать сообщения от клиентов, и что реально не имеет значения, который именно из потоков в пуле получит запрос. Это еще один аспект «канальной абстракции». В предыдущих версиях QNX (особенно в QNX4), клиент мог передать сообщение серверу, определяя его идентификатором узла (node ID) и идентификатором процесса (process ID) на этом узле. Поскольку QNX4 — однопоточная ОС, никакого беспорядка с тем, кому передается сообщение, в ней быть не могло. Однако, стоит ввести понятие потока, и встает дополнительная проблема адресации потоков в процессе (ведь именно потоки собственно предоставляют сервисы). Поскольку поток — вещь преходящая, в действительности для клиента не имеет смысла подключаться к четко определенному потоку в четко определенном процессе на четко определенном узле. К тому же, а что если нужный поток занят? Мы тогда должны были бы обеспечить клиенту возможность выбрать «незанятый поток из некоторого пула потоков, предоставляющих нужный сервис».
Так вот, для этого и существуют каналы. Канал — это «адрес» некоторого «пула потоков, предоставляющих нужный сервис». Суть здесь заключается в том, что вызвать функцию MsgReceive() по одному и тому же каналу могут несколько потоков одновременно. Все они будут блокированы, но входящее сообщение будет передано только одному из них.
Кто послал сообщение?
Довольно часто серверу необходимо знать, кто послал ему сообщение. Для этого есть ряд причин, например:
• учет клиентов;
• управление доступом;
• определение контекстных связей;
• выбор типа сервиса;
• и т.д.
Сделать так, чтобы клиент передавал серверу эту информацию с каждым сообщением, было бы излишне громоздким (да и давало бы лишние лазейки в системе защиты). Поэтому существует специальная структура, заполняемая ядром всякий раз, когда функция MsgReceive() разблокируется, приняв сообщение. Эта структура имеет тип struct _msg_info
и содержит в себе следующее:
struct _msg_info {
int nd;
int srcnd;
pid_t pid;
int32_t chid;
int32_t scoid;
int32_t coid;
int32_t msglen;
int32_t tid;
int16_t priority;
int16_t flags;
int32_t srcmsglen;
};
Вы передаете все это функции MsgReceive() в качестве последнего параметра. Если вы передаете NULL, то не произойдет ничего. (Информацию все равно можно будет потом получить с помощью вызова функции MsgInfo() — она не теряется!)
Давайте взглянем на поля этой структуры:
nd , srcnd , pid и tid |
Это дескриптор узла, идентификатор процесса и идентификатор потока клиента. (Заметьте, что nd — это дескриптор принимающего узла для режима передачи, a srcnd — это дескриптор передающего узла для режима приема. Для этого имеется очень серьезное основание ;-), которое мы рассмотрим ниже в разделе «Несколько замечаний о дескрипторах узлов»). |
priority |
Приоритет потока, пославшего сообщение. |
chid , coid |
Идентификатор канала, по которому сообщение было передано, и идентификатор использованного при этом соединения. |
scoid |
Идентификатор соединения с сервером. Это внутренний идентификатор, который применяется ядром для маршрутизации сообщения от сервера назад к клиенту Вам не нужно ничего знать об этом идентификаторе, кроме одного любопытно факта, что это будет небольшое целое число, которое уникально идентифицирует клиента. |
flags |
Содержит различные битовые флаги: _NTO_MI_ENDIAN_BIG, _NTO_MI_ENDIAN_DIFF, _NTO_MI_NET_CRED_DIRTY и _NTO_MI_UNBLOCK_REQ. Биты _NTO_MI_ENDIAN_BIG и _NTO_MI_ENDIAN_DIFF сообщат вам о порядке байт в слове для отправившей сообщение машины (в случае, если сообщение пришло через сеть от машины с другим порядком байт), бит _NTO_MI_NET_CRED_DIRTY зарезервирован для внутреннего использования, значение бита _NTO_MI_UNBLOCK_REQ мы рассмотрим в разделе «Использование бита _NTO_MI_UNBLOCK_REQ», см. ниже. |
msglen |
Число принятых байт. |
srcmsglen |
Длина исходного сообщения в байтах, как оно было отправлено клиентом. Это число может превышать значение msglen — например, в случае приема меньшего количества данных, чем было послано. Заметьте, что это поле действительно только в том случае, если установлен бит _NTO_CHF_SENDER_LEN в переданном функции ChannelCreate() (для канала, по которому было получено данное сообщение) параметре flags . |
Идентификатор отправителя (receive ID), он же клиентский жетон (client cookie)
В примере программы, представленном выше, отметьте следующее:
Читать дальше