Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
IPC.doc
Скачиваний:
0
Добавлен:
13.01.2020
Размер:
536.06 Кб
Скачать

Обмен сообщениями в сети

Пример:

#include <fcntl.h>

# include <unistd.h>

int

main (void)

{

int fd;

fd = open («/net/wintermute/home/rk/filename», O_WRONLY);

write (fd, «Это обмен сообщениями\n», 24);

close (fd) ;

return (EXIT_SUCCESS);

}

  1. Функции open() клиента было предписано открыть файл с именем, которое начинается с /net. (Имя /net — имя по умолчанию, объявляемое администратором qnet — см. документацию по QNX/Neutrino, раздел npi-qnet). Клиент понятия не имеет, кто именно отвечает за конкретное имя пути, поэтому он соединяется с администратором процессов (шаг 1), чтобы выяснить, кому принадлежит ресурс. Это выполняется автоматически и не зависит от того, передаем ли мы сообщения по сети или нет. Поскольку все ресурсы, имена которых начинаются с /net, принадлежат администратору qnet, администратор процессов отвечает клиенту, что относительно этого имени пути надо спросить администратор qnet.

  2. Клиент теперь посылает сообщение потоку администратора qnet, надеясь, что тот будет способен обработать запрос. Однако администратор qnet на этом узле не может предоставить клиенту конечный сервис, поэтому он сообщает клиенту, что тот должен обратиться к администратору процессов на узле wintermute. (Это делается специальным перенаправляющим сообщением, в котором содержатся ND/PID/CHID сервера, к которому надо обратиться взамен.) Это перенаправление также автоматически обрабатывается клиентской библиотекой.

  3. Клиент соединяется с администратором процессов на узле wintermute. Это включает в себя отправку сообщения другому узлу с помощью драйверного потока qnet. Процесс qnet клиентского узла получает сообщение и транспортирует его через сетевую среду удаленному qnet, который, в свою очередь, доставляет его администратору процессов на узле wintermute. Администратор процессов этого узла разрешает остальную часть имени пути (в нашем примере это /home/rk/filename) и отвечает перенаправляющим сообщением. Это сообщение передается обратно — через qnet сервера по сетевой среде к qoct клиента и, наконец, самому клиенту. Поскольку в этом сообщении содержатся ND/PID/CHID нужного сервера, предоставляющего конечный сервис, клиент теперь знает, к кому обращаться (в нашем примере это администратор удаленной файловой системы).

  4. Теперь клиент посылает запрос непосредственно нужному серверу. Маршрут следования сообщения здесь идентичен описанному в предыдущем пункте, за исключением того, что на этот раз связь с сервером осуществляется напрямую, а не через администратор процессов.

После того как пройдены этапы 1, 2 и 3, все дальнейшие коммуникации осуществляются аналогично этапу 4. В вышеприведенном примере все сообщения типа ореn(), read() и close() идут по маршруту, описанному в этапе 4, Заметьте, что вся последовательность рассмотренных событий была запущена вызовом ореn(), но само сообщение ореn() все равно дошло до сервера-адресата так, как это описано этапом 4.

Для особо любопытных: на самом деле в изложении пропущен один этап. На этапе 2, когда клиент спрашивает qnet об узле wintermute, qnet должен сначала выяснить, кто такой wintermute. Это может привести к еще одной сетевой транзакции для разрешения имени узла. Приведенный выше рисунок корректен, если предположить, что qnet заранее знал про узел с именем wintermute.

Итак, как только соединение установлено, все дальнейшие операции обмена сообщениями осуществляются в соответствии с этапом 4, как указано на рисунке. Это может привести к ошибочному представлению, что передача сообщений по сети идентична локальной. К сожалению, это не так. Вот список отличий:

• более длительные задержки;

• функция ConnectAttach() возвращает признак успешного соединения независимо от того, является ли узел доступным или нет — реальный признак ошибки проявляется только при первой попытке передать сообщение;

• функция MsgDeliverEvent() не обеспечивает достаточной надежности;

• функции MsgReply(), MsgRead(), MsgWrite() являются блокирующими вызовами (в локальном варианте они не являлись таковыми);

• функция MsgReceive() не будет принимать все данные, посланные клиентом; сервер будет должен вызывать функцию MsgRead() для получения окончательных остальных данных.

Поскольку передача сообщений теперь выполняется в некоторой среде, а не прямым копированием «память-память» под управлением ядра, можно смело ожидать, что затраты времени на передачу сообщения будут существенно выше (100-Мбитный Ethernet в сравнении с 128-битным динамическим ОЗУ с тактированием 100 МГц будет ориентировочно на один или два порядка медленнее). В дополнение к этому также будут сказываться накладные расходы протокола и повторные попытки передачи в сбойных сетях.

Когда вы вызываете функцию CannectAttach(), вы задаете ей идентификаторы ND, PID и CHID. Все, что при этом происходит в QNX/Neutrino, заключается в возврате ядром идентификатора соединения драйверному потоку qnet, изображенному выше на рисунке. Поскольку никакого сообщения еще не отправлено, вы не имеете информации о том, доступен ли узел, к которому вы только что подсоединились, или нет. В обычном случае это не проблема, потому что большинство клиентов не будет самостоятельно вызывать ConnectAttach() и скорее воспользуется библиотечной функцией open(), которая перед передачей сообщения «open» сама вызывает ConnectAttach()- Это практически немедленно дает информацию о доступности удаленного узла.

Когда сервер вызывает функцию MsgDeliverEvent() локально, ответственность за доставку события целевому потоку ложится на ядро. В сетевом варианте сервер также может, вызывать функцию MsgDeliverEvent(), но на этот раз ядро доставит «заготовку» этого события администратору qnet, возлагая на него ответственность за доставку этой «заготовки» удаленному qnet, который уже доставит реальное событие клиенту. Так вот, на стороне сервера с этим вызовом могут возникнуть проблемы, потому что он не является блокирующим. Это означает, после вызова MsgDeliverEvent() сервер продолжает выполняться, и поздно уже оглядываться и говорить «Знаете, очень не хочется вас огорчать, но помните тот вызов MsgDeliverEvent(). Так вот, он не сработал...»

Чтобы уберечь функции MsgReply(), MsgRead() и MsgWrite() от вышеупомянутой проблемы MsgDeliverEvent(), эти функции при использовании их в сети преобразуются в блокирующие вызовы. В локальном случае они бы просто передали данные и разблокировались; в сети же мы должны либо удостовериться, что данные были доставлены клиенту (в случае MsgReply()), либо собственно передать данные по сети клиенту или от него (в случае двух других функций).

Функция MsgReceive() (при использовании в сети) тоже оказывается под влиянием. На момент разблокирования функции MsgReceive() на стороне сервера qnet может еще не успеть передать все данные клиента. Это делается из соображений производительности.

В структуре struct_msg_info, передаваемой функции MsgReceive() в качестве последнего параметра, есть два флага:

-msglen указывает на фактическое количество данных, переданное функцией MsgReceive() (qnet любит передавать по 8Кб за один раз).

-srcmsglen указывает на количество данных, которое клиент хотел передать (определяется клиентом).

Таким образом, если бы клиент желал передать 1 мегабайт данных по сети, MsgReceive() сервера разблокировалась бы, установив параметр msglen в значение 8192 (указывая, что 8192 байта доступны в буфере); параметр srcmsglen при этом равнялся бы 1048576 (указывая, что клиент пытался переслать 1 мегабайт данных).

Затем сервер использует MsgRead() для получения остальной части данных из адресного пространства клиента.

Еще одна любопытная вещь, которой мы еще не касались в обсуждениях обмена сообщениями, — это дескрипторы узлов, для краткости обозначаемые «ND» (Node Descriptor).

Вспомните: в наших примерах мы использовали символьные имена узлов, например, /net/wintermute. В QNX4 (предыдущая версия QNX до появления QNX/Neutrino) вся работа в сети была основана на концепции идентификатора узла, небольшого целого числа, уникально определяющего узел сети. Таким образом, в терминах QNX4 мы говорили бы что-то вроде «узел 61», или «узел 1», и это отражалось бы и на вызовах функций тоже.

При работе в QNX/Neutrino все узлы внутренне представляются в сети! Имеется в виду, что узел wintermute может думать об узле spud как об узле с дескриптором 7, в то время как сам узел spud может думать, что дескриптор 7 соответствует узлу magenta. В приведенной ниже таблице сведены примерные дескрипторы узлов, которые могли бы использоваться для описания трех узлов: wintermute, spud и foobar (не путать с аббревиатурой FUBAR):

Узел wintermute spud foobar

winternute 0 7 4

spud 4 0 6

foobar 5 7 0

Обратите внимание, что каждый узел считает свой собственный дескриптор нулевым. Также отметьте, что для узла spud оба узла wintermute и foobar имеют дескриптор 7. Однако, для узла foobar узел wintermute имеет дескриптор 4, а узел spud — 6. Как упоминалось раньше, эти номера не уникальны в сети, но они уникальны на каждом узле. Вы можете относиться к ним, как к файловым дескрипторам — два процесса, когда обращаются к одному и тому же файлу, могут иметь для него как одинаковый дескриптор, так и нет — все зависит от того, кто, когда и который файл открывает.

К счастью, вам не надо беспокоиться о дескрипторах узлов по ряду причин:

1 Большинство осуществляемых операций обмена сообщениями «с внешним миром» будут реализовываться с помощью вызовов функций высокого уровня (таких как функция open(), приведенная в примере выше).

2 Дескрипторы узлов не кэшируются — предполагается, что получив дескриптор, он используется немедленно и забывается.

3 Существует ряд библиотечных функций, предназначенных для преобразования имени пути (например, /net/magenta) в дескриптор узла.

Чтобы работать с дескрипторами узлов, вам понадобится подключить файл <sys/netmgr.h>, потому что он содержит прототипы семейства функций netmgr_*( ).

Для преобразования строки в дескриптор узла используется функция netmgr_strtond(). После получения дескриптора узла его следует сразу же применить в вызове функции ConnectAttach(). Не пытайтесь сохранять его какой-либо структуре данных! Веским основанием для этого является то, что администратор сети может решить повторно использовать дескриптор после отключения всех соединений с узлом.

Так что если вы получили дескриптор «7» для узла /net/ magenta, подсоединились к нему, передали сообщение и затем отсоединились, то существует возможность того, что администратор сети заново назначит дескриптор «7» другому узлу.

Поскольку дескрипторы узлов в сети не уникальны, возникает вопрос: «А как передавать эти штуки по сети?» Очевидно, взгляды узла magenta и узла wintermute на дескриптор «7» будут радикально отличаться. Существуют два способа решены этой проблемы:

  • Не передавать по сети дескрипторы узлов и пользоваться символьными именами (например, /net/wintermute)

  • Применять функцию netmgr_remote_nd().

Первый метод хорош как универсальное решение. Второй метод достаточно удобен на практике.

int

netmgr_remote_nd (int remote_nd, int local_nd);

Эта функция принимает два параметра, где remote_nd — дескриптор узла целевой машины, а 1осаl_nd — дескриптор узла, который нужно преобразовать из точки зрения локальной машины в точку зрения целевой. Результатом является дескриптор узла, корректный с точки зрения заданной удаленной машины.

Например, пусть wintermute — имя нашей локальной машины. У нас есть дескриптор узла «7», который является корректным на нашей локальной машине и указывает на узел magenta. Мы хотели бы выяснить, какой дескриптор узла использует узел magenta для связи с нашим узлом:

int remote_nd;

int magenta_nd;

magenta_nd = netmgr_strtemd («/net/magenta», NULL); printf («ND узла magenta - %d\n», magenta_nd); remote_nd = netmgr_remote_nd (magenta_nd, ND__LOCAL_NODE) ; printf («С точки зрения узла magenta, наш ND - %d\n», remote nd) ;

Это программа могла бы вывести что-то вроде следующего:

ND узла magenta - 7

С точки зрения узла magenta, наш ND - 4

Это говорит о том, что на узле magenta нашему узлу соответствует дескриптор «4». (Обратите внимание на использование специальной константы ND_LOCAL_NODE, которая в действительности равна нулю, для указания на «локальный узел»).

Теперь вернемся к тому, о чем мы говорили в разделе «Кто послал сообщение?»). Параметр struct _msg_info содержит, среди всего прочего, два дескриптора узлов:

struct _msg_info

{

int nd;

int srcnd;

};

Мы определили в описании для этих двух полей, что:

• nd — дескриптор принимающего узла с точки зрения передающего;

• srcnd — дескриптор передающего узла с точки зрения принимающего.

Так, для приведенного выше примера, где узел wintermute - локальный, а узел magenta — удаленный, когда узел magenta посылает нам (узлу wintermute) сообщение, эти поля заполняются следующим образом:

• nd равен 7;

• srcnd равен 4.

Асинхронный обмен сообщениями

Очереди сообщений в стандарте POSIX

В стандарте POSIX предусмотрен набор не блокирующих механизмов обмена в виде очередей. Так же, как и программные каналы, очереди сообщений являются именованными объектами, которые взаимодействуют с читателями ("readers") и писателями ("writers"). Очередь сообщений действует на основе приоритетов, присваиваемых каждому сообщению, и поэтому имеет более сложную структуру, чем каналы, тем самым, позволяя приложениям более гибко управлять взаимодействием.

Для работы с очередями сообщений стандарта POSIX в ОС ONX Neutrino должен быть запущен администратор ресурсов очередей mqueue (message queue resource manager).

В ОС QNX Neutrino очереди сообщений стандарта POSIX реализуются посредством необязательного администратора ресурсов mqueue. В отличие от примитивов обмена сообщениями, используемых в QNX Neutrino, очереди сообщений стандарта POSIX располагаются вне ядра.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]