Шрифт:
Такой вызов разблокирует клиента (но не передаст ему никаких данных) и возвратит код EOK («успешное завершение»).
Как вариант, вы можете при желании возвратить клиенту код ошибки. Вы не сможете сделать это с помощью функции MsgReply, вместо нее для этого используется функция MsgError:
В приведенном выше примере сервер обнаруживает, что клиент пытается записать данные в файловую систему, предназначенную только для чтения, и вместо данных возвращает клиенту код ошибки (errno) EROFS.
Еще одним поводом ответить клиенту без данных (и соответствующие вызовы мы вскоре рассмотрим) может быть то, что данные уже переданы ранее (с помощью функции MsgWrite), и больше никаких данных нет.
Почему применяются два типа вызовов? Они немного различны. В то время как обе функции MsgError и MsgReply разблокируют клиента, функция MsgError при этом не передаст никаких данных, заставит функцию MsgSend клиента возвратить -1 и установит переменную errno на стороне клиента в значение, переданное функции MsgError в качестве второго аргумента.
С другой стороны, функция MsgReply может передавать данные (как видно из ее третьего и четвертого параметров) и заставляет функцию MsgSend клиента возвратить значение, переданное MsgReply в качестве второго аргумента. Переменная errno клиента остается нетронутой.
В общем случае, если вам нужно только сообщить о результатах действия («прошло/не прошло»), лучше применять функцию MsgError. Если бы вы возвращали данные, здесь была бы необходима функция MsgReply. Обычно, когда вы возвращаете данные, вторым параметром функции MsgReply будет положительное целое число, указывающее на число возвращаемых байт.
Ранее мы отметили, что для соединения с сервером функции ConnectAttach необходимо указать дескриптор узла (Node Descriptor — ND), идентификатор процесса (process ID — PID), а также идентификатор канала (Channel ID — CHID). До настоящего момента мы не обсуждали, как именно клиент находит эту информацию.
Если один процесс создает другой процесс, тогда это просто — вызов создания процесса возвращает идентификатор вновь созданного процесса. Создающий процесс может либо передать собственные PID и CHID вновь созданному процессу в командной строке, либо вновь созданный процесс может вызвать функцию getppid для получения идентификатора родительского процесса, и использовать некоторый «известный» идентификатор канала.
А что если у нас два совершенно чужих процесса? Это возможно, например, в том случае, если сервер создан некоей третьей стороной, а вашему приложению нужно уметь общаться с этим сервером. Реально мы должны найти ответ на вопрос: «Как сервер объявляет о своем местонахождении?»
Существует множество способов сделать это; мы рассмотрим только три из них, в порядке возрастания «элегантности»:
1. Открыть файла с известным именем и сохранить в нем ND/PID/CHID. Такой метод является традиционным для серверов UNIX, когда сервер открывает файл (например,
2. Использовать для объявления идентификаторов ND/PID/CHID глобальные переменные. Такой способ обычно применяется в многопоточных серверах, которые могут посылать сообщение сами себе. Этот вариант по самой своей природе является очень редким.
3. Занять часть пространства имен путей и стать администратором ресурсов. Мы поговорим об этом в главе «Администраторы ресурсов».
Первый подход относительно прост, но он чреват «загрязнением файловой системы», когда в каталоге
Имеется и другая связанная с этим подходом проблема. Поскольку процесс, который создал файл, может умереть, не удалив этот файл, то вы не сможете узнать, жив ли еще этот процесс, пока не попробуете передать ему сообщение. И это ещё не самое страшное — еще хуже, если комбинация ND/PID/CHID указанная в файле, оказывается настолько старой, что может быть повторно использована другой программой! Получив «чужое» сообщение, эта программа в лучшем случае его проигнорирует его, а ведь может и предпринять некорректные действия. Так что такой подход исключается.
Второй подход, где мы используем глобальные переменные для объявления значений ND/PID/CHID, не является общим решением проблемы, поскольку в нем предполагается способность клиента обратиться к этим глобальным переменным. А поскольку для этого требуется использование разделяемой памяти, это не будет работать в сети! Так что этот метод обычно используется либо в небольших тестовых программах, либо в очень специфичных случаях, но всегда в контексте многопоточной программы.
Что реально происходит, так это то, что один поток в программе является клиентом, а другой поток — сервером. Поток-сервер создает канал и затем размещает идентификатор канала в глобальной переменной (идентификаторы узла и процесса являются одинаковыми для всех потоков в процессе, так что объявлять их не обязательно). Поток-клиент затем берет этот идентификатор канала и выполняет по нему функцию ConnectAttach.