Системное программирование в среде Windows
Шрифт:
Перейдем к рассмотрению последовательности операций, выполняемых
Обратите внимание, что клиент и сервер состязаются за ресурсы. Прежде всего, клиентский вызов функции WaitNamedPipe завершится ошибкой, если именованный канал к этому моменту еще не был создан сервером; для краткости тестирование успешности выполнения в нашем примере опущено, однако оно включено в примеры программ, доступные на Web-сайте. Далее, в редких случаях вызов CreateFile может быть выполнен еще до того, как сервер вызовет функцию ConnectNamedPipe. В этом случае функция ConnectNamedPipe вернет серверу значение FALSE, однако взаимодействия посредством именованного канала по-прежнему будет функционировать надлежащим образом.
Экземпляр именованного канала является глобальным ресурсом, поэтому, когда клиент разрывает соединение с сервером, к нему может подключиться другой клиент.
Функции транзакций именованных каналов
На рис. 11.2 показана типичная конфигурация клиента, в которой клиент выполняет следующие операции:
• Открывает экземпляр канала, создавая долговременное соединение с сервером и занимая экземпляр канала.
• Периодически посылает запросы и ожидает получения ответов.
• Закрывает соединение.
Встречающуюся здесь последовательность вызовов функций WriteFile и ReadFile можно рассматривать как единую клиентскую транзакцию, и Windows предоставляет соответствующую функцию для каналов сообщений:
Смысл всех параметров здесь должен быть ясен, поскольку данная функция сочетает в себе функции WriteFile и ReadFile, применяемые к дескриптору именованного канала. Указываются как выходной, так и входной буфер, а разыменованный указатель lpcbRead предоставляет размер сообщения. Перекрывающиеся операции (глава 14) возможны, однако в более типичных случаях функция ожидает ответа.
Функция TransactNamedPipe удобна в использовании, однако, как показывает рис. 11.2, она требует создания постоянного соединения, что ограничивает число возможных клиентов [32] .
Ниже приводится прототип второй клиентской вспомогательной функции.
Функция CallNamedPipe не требует образования постоянного соединения; вместо этого она создает временное соединение, объединяя в себе выполнение следующей последовательности операций:
32
Заметьте,
Преимуществом такого способа является лучшее использование канала за счет снижения накладных расходов системных ресурсов на один запрос.
Смысл параметров этой функции тот же, что и в случае функции TransactNamedPipe, если не считать того, что вместо дескриптора для указания канала используется его имя. Функция CallNamedPipe выполняется синхронном режиме (отсутствует структура OVERLAPPED). Указываемая при ее вызове длительность периода ожидания (dwTimeOut) (в миллисекундах) относится к соединению, а не транзакции. Параметр dwTimeOut имеет три специальных значения:
• NMPWAIT_NOWAIT
• NMPWAIT_WAIT_FOREVER
• NMPWAIT_USE_DEFAULT_WAIT, которое приводит к использованию интервала ожидания по умолчанию, заданного в вызове функции CreateNamedPipe.
Определение наличия сообщений в именованных каналах
В дополнение к возможности чтения данных из именованного канала с помощью функции ReadFile можно также определить, имеются ли в канале фактические сообщения, используя для этого функцию PeekNamedPipe. Это средство может быть использовано для опроса именованного канала (неэффективная операция), определения размера сообщения, чтобы распределить память для буфера перед выполнением чтения, или просмотра поступающих сообщений с целью назначения им приоритетов для последующей обработки.
Функция PeekNamedPipe обеспечивает считывание любого байта или сообщения из канала без их удаления, но ее невозможно блокировать, и она осуществляет возврат сразу же по завершении выполнения.
Чтобы определить, имеются ли в канале данные, необходимо проверить значение *lpcbAvail; если данные в канале присутствуют, оно должно быть больше 0. В этом случае параметры lpBuffer и lpcbRead могут иметь значения NULL. Если же буфер определен параметрами lpBuffer и cbBuffer, то значение *lpcbMessage укажет вам, остается ли еще некоторое количество байтов сообщений, которые не умещаются в буфере, что позволяет распределять буфер большего размера, прежде чем осуществлять чтение из именованного канала. Для канала, работающего в режиме считывания байтов, это значение равно 0.
Следует помнить, что функция PeekNamedPipe осуществляет чтение, не уничтожая данные, и поэтому для удаления сообщений или байтовых данных из канала требуется последующее применение функции ReadFile.
Каналы UNIX FIFO аналогичны именованным каналам и, таким образом, обеспечивают взаимодействие не связанных между собой процессов. Однако по сравнению с именованными каналами Windows их возможности являются несколько ограниченными:
• Каналы FIFO являются полудуплексными.
• Каналы FIFO действуют только в пределах одного компьютера.
• Каналы FIFO ориентированы на работу с байтами, поэтому в клиент-серверных приложениях проще всего использовать записи фиксированной длины. Тем не менее, отдельные операции чтения и записи являются атомарными.
Сервер, на котором применяется это средство, должен использовать для каждого ответа клиентам отдельный канал FIFO, хотя все клиенты могут посылать запросы по одному и тому же известному каналу. В соответствии с общепринятой практикой клиенты включают имя канала FIFO в запрос соединения.
Функция UNIX mkfifo является ограниченной версией функции CreateNamedFile.
Если клиенты и сервер должны находиться в сети, используйте сокеты или аналогичный механизм транспортировки сетевых сообщений. Сокеты работают в дуплексном режиме, однако требуют использования отдельного соединения для каждого клиента.