Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
Передача меньшего объема данных, чем предполагается.
Ядро реально передает минимум из двух указанных размеров. В нашем случае ядро передало бы 28 байт, сервер бы разблокировался и отобразил сообщение клиента. Оставшиеся 484 байта (из буфера длиной 512 байт) остались бы нетронутыми.
Аналогичная ситуация с функцией MsgReply. Функция MsgReply информирует, что собирается передать 512 байт, но функция MsgSend определила, что может принять максимум 200 байт. Ядро опять передает минимум. В этом случае 200 байтов, которые клиент может принять, ограничивают размер передачи. (Один интересный аспект здесь состоит в том, что когда сервер передаст данные, то если клиент не примет их целиком, как в нашем примере, их уже нельзя будет вернуть — они будут потеряны.).
Когда мы будем обсуждать обмен сообщениями по сети, вы увидите, что в количестве передаваемых данных есть кое-какое «ага». Мы проанализируем это далее в разделе «Особенности обмена сообщениями в сети».
Иерархический принцип обмена (send-иерархия)
В обмене сообщениями есть одна вещь, которая, возможно, не является очевидной — это необходимость следовать строгой иерархии обмена. Означает это то, что два потока никогда не должны посылать сообщения друг другу; наоборот, они должны быть организованы так, что каждый поток занимал свой «уровень иерархии», и все потоки данного уровня должны посылать сообщения только потокам более низкого уровня, а не своего или высшего. Проблема с наличием двух потоков, которые посылают сообщения друг другу, заключается в том, что в конечном счете вы столкнетесь с проблемой взаимной блокировки (deadlock), когда оба потока ожидают друг от друга ответ на соответствующие сообщения. Поскольку эти потоки блокированы, то они никогда не будут поставлены на выполнение, а значит, не смогут дать друг другу ответ, и вы в результате получите два (а то и более!) зависших потока.
Способ назначения потокам уровней иерархии заключается в том, чтобы разместить наиболее удаленную клиентуру на самом верхнем уровне и работать оттуда. Например, если у вас есть графический интерфейс пользователя, который использует некоторый сервер баз данных, который, в свою очередь, использует файловую систему, а файловая система использует блок-ориентированный драйвер файловой системы, то у вас получается естественная иерархия процессов.
Передачи (sends) при обмене сообщениями будут направлены от клиента (графического интерфейса пользователя) вниз к серверам нижнего уровня; ответы на сообщения (replies) будут иметь встречное направление.
При том, что это работает в большинстве случаев, вы можете столкнуться и с ситуацией, когда вам придется нарушить иерархию обмена. Это никогда не следует выполнять простым нарушением иерархии, направляя сообщения «против течения» — для этого существует функция MsgDeliverEvent, о которой речь несколько позже.
Идентификаторы отправителя, каналы и другие параметры
Мы с вами пока не обсуждали различные параметры, используемые в ранее рассмотренных примерах, чтобы можно было сконцентрировать внимание на самих принципах обмена сообщениями. Теперь поговорим об этих параметрах более подробно.
В приведенном выше примере с сервером мы видели, что сервер создал один-единственный канал. Конечно, можно было создать больше, но обычно серверы так не делают. (Наиболее очевидный пример сервера с двумя каналами — это администратор штатной сети
Оказывается, что в действительности нет большой необходимости в создании нескольких каналов. Главное назначение канала состоит в том, чтобы четко указать серверу, где «слушать» на предмет входящих сообщений, и четко указать клиентам, куда передавать сообщения (через соответствующие соединения). Единственная ситуация, когда вам могло бы понадобиться использовать несколько каналов в сервере, — это если бы хотели реализовать сервер, предоставляющий различные услуги (или различные классы услуг) в зависимости от того, по какому каналу было принято сообщение. Второй канал мог бы применяться, например, для отправки сообщений типа «импульс», пробуждающих субсерверы — это гарантировало бы развязку этого сервиса от служебных функций, предоставляемых обычными сообщениями по первому каналу.
В предыдущем параграфе я утверждал, что вы могли бы использовать в сервере пул потоков, готовый принимать сообщения от клиентов, и что реально не имеет значения, который именно из потоков в пуле получит запрос. Это еще один аспект «канальной абстракции». В предыдущих версиях QNX (особенно в QNX4), клиент мог передать сообщение серверу, определяя его идентификатором узла (node ID) и идентификатором процесса (process ID) на этом узле. Поскольку QNX4 — однопоточная ОС, никакого беспорядка с тем, кому передается сообщение, в ней быть не могло. Однако, стоит ввести понятие потока, и встает дополнительная проблема адресации потоков в процессе (ведь именно потоки собственно предоставляют сервисы). Поскольку поток — вещь преходящая, в действительности для клиента не имеет смысла подключаться к четко определенному потоку в четко определенном процессе на четко определенном узле. К тому же, а что если нужный поток занят? Мы тогда должны были бы обеспечить клиенту возможность выбрать «незанятый поток из некоторого пула потоков, предоставляющих нужный сервис».
Так вот, для этого и существуют каналы. Канал — это «адрес» некоторого «пула потоков, предоставляющих нужный сервис». Суть здесь заключается в том, что вызвать функцию MsgReceive по одному и тому же каналу могут несколько потоков одновременно. Все они будут блокированы, но входящее сообщение
будет передано только одному из них.Довольно часто серверу необходимо знать, кто послал ему сообщение. Для этого есть ряд причин, например:
• учет клиентов;
• управление доступом;
• определение контекстных связей;
• выбор типа сервиса;
• и т.д.
Сделать так, чтобы клиент передавал серверу эту информацию с каждым сообщением, было бы излишне громоздким (да и давало бы лишние лазейки в системе защиты). Поэтому существует специальная структура, заполняемая ядром всякий раз, когда функция MsgReceive разблокируется, приняв сообщение. Эта структура имеет тип
Вы передаете все это функции 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. |
В примере программы, представленном выше, отметьте следующее:
Это — ключевой фрагмент, потому что именно в нем иллюстрируется привязка приема сообщения от клиента к последующему ответу этому конкретному клиенту. Идентификатор отправителя — это целое число, которое действует как жетон («magic cookie»), который вы получаете от клиента и обязаны хранить, если вы желаете впоследствии взаимодействовать с этим клиентом. Что произойдет, если вы его потеряете? Его больше нет. Функция MsgSend клиента не разблокируется, пока вы (конкретный сервер) живы, или пока не произошел тайм-аут обмена сообщениями (и даже в этом случае все не так просто; см. функцию TimerTimeout в справочном руководстве по библиотеке Си и обсуждение о применения в главе «Часы, таймеры и периодические уведомления», раздел «Тайм-ауты ядра»).