Каналы именованные каналы windows

Содержание
  1. Сетевой доступ: разрешать анонимный доступ к именованным каналам. Network access: Named Pipes that can be accessed anonymously
  2. Справочные материалы Reference
  3. Возможные значения Possible values
  4. Рекомендации Best practices
  5. Расположение Location
  6. Значения по умолчанию Default values
  7. Управление политикой Policy management
  8. Необходимость перезапуска Restart requirement
  9. Групповая политика Group Policy
  10. Вопросы безопасности Security considerations
  11. Уязвимость Vulnerability
  12. Противодействие Countermeasure
  13. Возможное влияние Potential impact
  14. Межпроцессный обмен
  15. Организация каналов в ОC Windows
  16. Анонимные каналы
  17. Прогон программы общения процесса через анонимный канал с самим собой
  18. Прогон программы общения через анонимный канал клиента и сервера
  19. Именованные каналы
  20. Использование именованных каналов
  21. Прогон программы общения двух процессов через именованный канал
  22. Заключение
  23. ГЛАВА 11
  24. Анонимные каналы
  25. Пример: перенаправление ввода/вывода с использованием анонимного канала
  26. Именованные каналы
  27. Использование именованных каналов
  28. Создание именованных каналов
  29. Подключение клиентов именованных каналов
  30. Функции состояния именованных каналов
  31. Функции подключения именованных каналов
  32. Подключение клиентов и серверов именованных каналов
  33. Функции транзакций именованных каналов
  34. Определение наличия сообщений в именованных каналах
  35. Пример: клиент-серверный процессор командной строки
  36. Комментарии по поводу клиент-серверного процессора командной строки
  37. Почтовые ящики
  38. Использование почтовых ящиков
  39. Создание и открытие почтового ящика
  40. Создание, подключение и именование каналов и почтовых ящиков
  41. Пример: сервер, обнаруживаемый клиентами
  42. Комментарии по поводу многопоточных моделей
  43. Резюме
  44. В следующих главах
  45. Упражнения
  46. Примечания:

Сетевой доступ: разрешать анонимный доступ к именованным каналам. Network access: Named Pipes that can be accessed anonymously

Область применения Applies to

В этой статье описываются лучшие методики, расположение, значения, управление политиками и вопросы безопасности для сетевого доступа: именовать конвейеры, к которые можно получить анонимный доступ к параметру политики безопасности. Describes the best practices, location, values, policy management and security considerations for the Network access: Named Pipes that can be accessed anonymously security policy setting.

Справочные материалы Reference

Этот параметр политики определяет, какие сеансы связи или каналы связи имеют атрибуты и разрешения, разреша которыми анонимный доступ. This policy setting determines which communication sessions, or pipes, have attributes and permissions that allow anonymous access.

Ограничение доступа по именированным каналам, таким как COMNAP и LOCATOR, помогает предотвратить несанкционированный доступ к сети. Restricting access over named pipes such as COMNAP and LOCATOR helps prevent unauthorized access to the network.

Возможные значения Possible values

  • Пользовательский список общих папок User-defined list of shared folders
  • Не определено Not defined

Рекомендации Best practices

  • Установите для этой политики значение null; то есть введите параметр политики, но не вводите именовые конвейеры в текстовом поле. Set this policy to a null value; that is, enable the policy setting, but do not enter named pipes in the text box. Это отключит доступ к сеансу null по именовамым каналам, и приложения, которые используют эту функцию или неавтерицированный доступ к именовамым каналам, перестают работать. This will disable null session access over named pipes, and applications that rely on this feature or on unauthenticated access to named pipes will no longer function.

Расположение Location

Конфигурация компьютера\Параметры Windows\Параметры безопасности\Локальные политики\Параметры безопасности Computer Configuration\Windows Settings\Security Settings\Local Policies\Security Options

Значения по умолчанию Default values

В следующей таблице перечислены фактические и эффективные значения по умолчанию для этой политики. The following table lists the actual and effective default values for this policy. Значения по умолчанию также можно найти на странице свойств политики. Default values are also listed on the policy’s property page.

Тип сервера или объект групповой политики Server type or GPO Значение по умолчанию Default value
Default Domain Policy Default Domain Policy Не определено Not defined
Политика контроллера домена по умолчанию Default Domain Controller Policy Netlogon, samr, lsarpc Netlogon, samr, lsarpc
Параметры по умолчанию для автономного сервера Stand-Alone Server Default Settings Null Null
Эффективные параметры по умолчанию для DC DC Effective Default Settings Netlogon, samr, lsarpc Netlogon, samr, lsarpc
Действующие параметры по умолчанию для рядового сервера Member Server Effective Default Settings Не определено Not defined
Действующие параметры по умолчанию для клиентского компьютера Client Computer Effective Default Settings Не определено Not defined

Управление политикой Policy management

В этом разделе описываются различные функции и средства, которые помогут вам управлять этой политикой. This section describes different features and tools available to help you manage this policy.

Необходимость перезапуска Restart requirement

Нет. None. Изменения этой политики становятся эффективными без перезапуска устройства, если они сохраняются локально или распространяются посредством групповой политики. Changes to this policy become effective without a device restart when they are saved locally or distributed through Group Policy.

Групповая политика Group Policy

Чтобы этот параметр политики вступил в силу, необходимо также включить сетевой доступ: ограничить анонимный доступ к именованным каналам и ресурсам. For this policy setting to take effect, you must also enable the Network access: Restrict anonymous access to Named Pipes and Shares setting.

Вопросы безопасности Security considerations

В этом разделе описывается, каким образом злоумышленник может использовать компонент или его конфигурацию, как реализовать меры противодействия, а также рассматриваются возможные отрицательные последствия их реализации. This section describes how an attacker might exploit a feature or its configuration, how to implement the countermeasure, and the possible negative consequences of countermeasure implementation.

Уязвимость Vulnerability

Вы можете ограничить доступ по именированным каналам, таким как COMNAP и LOCATOR, чтобы предотвратить несанкционированный доступ к сети. You can restrict access over named pipes such as COMNAP and LOCATOR to help prevent unauthorized access to the network. В следующем списке описываются доступные именуемые конвейеры и их назначение. The following list describes available named pipes and their purpose. Эти конвейеры были предоставлены анонимным доступом в более ранних версиях Windows, и некоторые устаревшие приложения могут по-прежнему использовать их. These pipes were granted anonymous access in earlier versions of Windows and some legacy applications may still use them.

Именоваемая конвейерная Named pipe Описание Purpose
COMNAP COMNAP Именоваемая конвейерная база SNABase. SNABase named pipe. Системная архитектура сети (SNA) — это набор сетевых протоколов, изначально разработанных для компьютеров с ibm mainframe. Systems network Architecture (SNA) is a collection of network protocols that were originally developed for IBM mainframe computers.
COMNODE COMNODE SNA Server с именем pipe. SNA Server named pipe.
SQL\QUERY SQL\QUERY Именователь по умолчанию для SQL Server. Default named pipe for SQL Server.
SPOOLSS SPOOLSS Именователь канала для службы печати пулов. Named pipe for the Print Spooler service.
EPMAPPER EPMAPPER Конечный mapper с именем pipe. End Point Mapper named pipe.
LOCATOR LOCATOR Служба удаленного вызова процедуры с именем pipe. Remote Procedure Call Locator service named pipe.
TrlWks TrlWks Распределенный клиент отслеживания ссылок с именем pipe. Distributed Link Tracking Client named pipe.
TrkSvr TrkSvr Распределенный сервер отслеживания ссылок с именем pipe. Distributed Link Tracking Server named pipe.

Противодействие Countermeasure

Настройте сетевой доступ: именуемые конвейеры, к которые можно получить анонимный доступ, установив значение NULL (в этом параметре можно включить, но не указывать именоваемые конвейеры в текстовом поле). Configure the Network access: Named Pipes that can be accessed anonymously setting to a null value (enable the setting but do not specify named pipes in the text box).

Возможное влияние Potential impact

Эта конфигурация отключает доступ к сеансу null по именовамым каналам, и приложения, которые используют эту функцию или неавтерицированный доступ к именовамым каналам, больше не работают. This configuration disables null-session access over named pipes, and applications that rely on this feature or on unauthenticated access to named pipes no longer function. Это может нарушить доверие между доменами Windows Server 2003 в смешанном режиме. This may break trust between Windows Server 2003 domains in a mixed mode environment.

Межпроцессный обмен

Организация каналов в ОC Windows

Анонимные каналы

Анонимные каналы в Windows — это полудуплексное средство потоковой передачи байтов между родственными процессами. Они функционируют в пределах локальной вычислительной системы и хорошо подходят для перенаправления выходного потока одной программы на вход другой. Анонимные каналы реализованы при помощи именованных каналов с уникальными именами.

Анонимные каналы создаются процессом сервером при помощи функции CreatePipe :

Функция CreatePipe возвращает два описателя (дескриптора) для чтения и записи в канал. После создания канала необходимо передать клиентскому процессу эти дескрипторы (или один из них), что обычно делается с помощью механизма наследования.

Для наследования описателя нужно, чтобы дочерний процесс создавался функцией CreateProcess с флагом наследования TRUE . Предварительно нужно создать наследуемые описатели. Это можно, например, сделать путем явной спецификации параметра bInheritHandle структуры SECURITY_ATTRIBUTES при создании канала.

Другим способом является создание наследуемого дубликата имеющегося описателя при помощи функции DuplicateHandle и последующая передача его создаваемому процессу через командную строку или каким-либо иным образом.

Получив нужный описатель, клиентский процесс, так же как и сервер, может далее взаимодействовать с каналом при помощи функций ReadFile и WriteFile . По окончании работы с каналом оба процесса должны закрыть описатели при помощи функции CloseHandle .

Прогон программы общения процесса через анонимный канал с самим собой

В приведенной программе создается анонимный канал, в него записывается строка цифр, затем часть этой строки читается и выводится на экран.

Прогон программы общения через анонимный канал клиента и сервера

В качестве самостоятельного упражнения рекомендуется организовать через анонимный канал межпроцессное взаимодействие . Процесс-сервер должен создать канал, записать в него информацию и запустить процесс-клиент, задача которого — записанную информацию из канала извлечь.

Именованные каналы

Именованные каналы являются объектами ядра ОС Windows, позволяющими организовать межпроцессный обмен не только в изолированной вычислительной системе, но и в локальной сети. Они обеспечивают дуплексную связь и позволяют использовать как потоковую модель , так и модель, ориентированную на сообщения. Обмен данными может быть синхронным и асинхронным.

Каналы должны иметь уникальные в рамках сети имена в соответствии с правилами именования ресурсов в сетях Windows (Universal Naming Convention , UNC), например, \\ServerName\pipe\PipeName . Для общения внутри одного компьютера имя записывается в форме \\.\pipe\PipeName , где «.» обозначает локальную машину. Слово «pipe» в составе имени фиксировано, а PipeName — имя, задаваемое пользователем. Эти имена, подобно именам открытых файлов, не являются именами объектов. Они относятся к пространству имен под управлением драйверов файловых систем именованных каналов ( \Winnt\System32\Drivers\Npfs.sys ), привязанному к специальному объекту устройству \Device\NamedPipe , на которое есть ссылка в каталоге глобальных имен объектов \??\Pipe (эти последние имена «видит» утилита WinObj).

Имена созданных именованных каналов можно перечислить с помощью свободно распространяемой утилиты pipelist с сайта http://www.sysinternals.com. Поскольку имена каналов интегрированы в общую структуру имен объектов, приложения могут открывать именованные каналы с помощью функции CreateFile и взаимодействовать с ними через функции ReadFile и WriteFile .

Использование именованных каналов

Сервер создает именованный канал при помощи функции CreateNamedPipe (см. MSDN).

Помимо имени канала в форме, описанной выше, в число параметров функции входят: флаг, указывающий модель передачи данных; параметр, определяющий синхронный или асинхронный режим работы канала, а также указывающий, должен ли канал быть односторонним или двухсторонним. Кроме того, имеется необязательный дескриптор защиты, запрещающий несанкционированный доступ к именованному каналу, и параметр, определяющий максимальное число одновременных соединений по данному каналу.

Повторно вызывая CreateNamedPipe , можно создавать дополнительные экземпляры этого же канала.

После вызова CreateNamedPipe сервер выполняет вызов ConnectNamedPipe и ждет отклика от клиентов, которые соединяются с каналом при помощи функции CreateFile или CallNamedPipe , указывая при вызове имя созданного сервером канала. Легальный клиент получает описатель, представляющий клиентскую сторону именованного канала , и работа серверной функции ConnectNamedPipe на этом завершается.

После того как соединение по именованному каналу установлено, клиент и сервер могут использовать его для чтения и записи данных через Win32-функции ReadFile и WriteFile .

Прогон программы общения двух процессов через именованный канал

В качестве упражнения рекомендуется осуществить прогон программы общения клиента и сервера через именованный канал .

В данном примере сервер создает канал, затем запускает процесс-клиент и ждет соединения. Далее он читает сообщение, посланное клиентом.

Помимо перечисленных выше система представляет еще ряд полезных функций для работы с именованными каналами. Для копирования данных из именованного канала без удаления их из канала используется функция PeekNamedPipe . Функция TransactNamedPipe применяется для объединения операций чтения и записи в канал в одну операцию, которая называется транзакцией. Имеются информационные функции для определения состояния канала, например, GetNamedPipeHandleState или GetNamedPipeInfo . Полный перечень находится в MSDN.

Заключение

Организация совместной деятельности и общения процессов является важной и актуальной задачей. К основным способам межпроцессного обмена традиционно относят каналы и разделяемую память , для организации которых используют разделяемые ресурсы. Анонимные каналы поддерживают потоковую модель , в рамках которой данные представляют собой неструктурированную последовательность байтов. Именованные каналы, поддерживающие как потоковую модель , так и модель, ориентированную на сообщения, обеспечивают обмен данными не только в изолированной вычислительной среде, но и в локальной сети.

ГЛАВА 11

Взаимодействие между процессами

В главе 6 было показано, как создавать процессы и управлять ими, тогда как главы 7—10 были посвящены описанию методов управления потоками, которые выполняются внутри процессов, и объектов, обеспечивающих их синхронизацию. Вместе с тем, если не считать использования разделяемой памяти, мы до сих пор не рассмотрели ни одного из методов взаимодействия между процессами.

Ниже вы ознакомитесь с последовательным межпроцессным взаимодействием (Interprocess Communication, IPC)[30], в котором используются объекты, подобные файлам. Двумя основными механизмами Windows, реализующими IPC, являются анонимные и именованные каналы, доступ к которым осуществляется с помощью уже известных вам функций ReadFile и WriteFile. Простые анонимные каналы являются символьными и работают в полудуплексном режиме. Эти свойства делают их удобными для перенаправления выходных данных одной программы на вход другой, как это обычно делается в UNIX. В первом примере демонстрируется, как реализовать эту возможность.

По сравнению с анонимными каналами возможности именованных каналов гораздо богаче. Они являются дуплексными, ориентированы на обмен сообщениями и обеспечивают взаимодействие через сеть. Кроме того, один именованный канал может иметь несколько открытых дескрипторов. В сочетании с удобными, ориентированными на выполнение транзакций функциями эти возможности делают именованные каналы пригодными для создания клиент-серверных систем. Это демонстрируется во втором из приведенных в настоящей главе примере, представляющем многопоточный клиент-серверный командный процессор, моделируемый в соответствии с рис. 7.1, который привлекался для обсуждения потоков. Каждый из потоков сервера управляет взаимодействием с отдельным клиентом, и для каждой пары «поток/клиент» используется отдельный дескриптор, то есть отдельный экземпляр именованного канала.

Наконец, почтовые ящики обеспечивают широковещательную рассылку сообщений по схеме «один многим», а их использование для расширения возможностей командного процессора демонстрируется в последнем примере.

Анонимные каналы

Анонимные каналы (anonymous channels) Windows обеспечивают однонаправленное (полудуплексное) посимвольное межпроцессное взаимодействие. Каждый канал имеет два дескриптора: дескриптор чтения (read handle) и дескриптор записи (write handle). Функция, с помощью которой создаются анонимные каналы, имеет следующий прототип:

BOOL CreatePipe(PHANDLE phRead, PHANDLE phWrite, LPSECURITY_ATTRIBUTES lpsa, DWORD cbPipe)

Дескрипторы каналов часто бывают наследуемыми; причины этого станут понятными из приведенного ниже примера. Значение параметра cbPipe, указывающее размер канала в байтах, носит рекомендательный характер, причем значению 0 соответствует размер канала по умолчанию.

Чтобы канал можно было использовать для IPC, должен существовать еще один процесс, и для этого процесса требуется один из дескрипторов канала. Предположим, например, что родительскому процессу, вызвавшему функцию CreatePipe, необходимо вывести данные, которые нужны дочернему процессу. Тогда возникает вопрос о том, как передать дочернему процессу дескриптор чтения (phRead). Родительский процесс осуществляет это, устанавливая дескриптор стандартного ввода в структуре STARTUPINFO для дочерней процедуры равным *phRead.

Чтение с использованием дескриптора чтения канала блокируется, если канал пуст. В противном случае в процессе чтения будет воспринято столько байтов, сколько имеется в канале, вплоть до количества, указанного при вызове функции ReadFile. Операция записи в заполненный канал, которая выполняется с использованием буфера в памяти, также будет блокирована.

Наконец, анонимные каналы обеспечивают только однонаправленное взаимодействие. Для двухстороннего взаимодействия необходимы два канала.

Пример: перенаправление ввода/вывода с использованием анонимного канала

В программе 11.1 представлен родительский процесс, который создает два процесса из командной строки и соединяет их каналом. Родительский процесс устанавливает канал и осуществляет перенаправление стандартного ввода/вывода. Обратите внимание на то, каким образом задается свойство наследования дескрипторов анонимного канала и как организуется перенаправление стандартного ввода/вывода на два дочерних процесса; эти методики описаны в главе 6.

Местоположение оператора WriteFile в блоке Program2 на рис. 11.1 справа предполагает, что программа считывает большой объем данных, обрабатывает их, и лишь после этого записывает результаты. Эту запись можно было бы осуществлять и внутри цикла, выводя результаты после каждого считывания.

Рис. 11.1. Межпроцессное взаимодействие с использованием анонимного канала

Дескрипторы каналов и потоков должны закрываться при первой же возможности. На рис. 11.1 закрытие дескрипторов не отражено, однако это делается в программе 11.1. Родительский процесс должен закрыть дескриптор устройства стандартного вывода сразу же после создания первого дочернего процесса, чтобы второй процесс мог распознать метку конца файла, когда завершится выполнение первого процесса. В случае существования открытого дескриптора первого процесса второй процесс не смог бы завершиться, поскольку система не обозначила бы конец файла.

В программе 11.1 используется непривычный синтаксис: две команды, разделенные символом =, обозначающим канал. Использование для этой цели символа вертикальной черты (|) привело бы к возникновению конфликта с системным командным процессором. Рисунок 11.1 является схематическим представлением выполнения следующей команды:

$ pipe Program1 аргументы = Program2 аргументы

При использовании средств командного процессора UNIX или Windows соответствующая команда имела бы следующий вид:

Программа 11.1. pipe: межпроцессное взаимодействие с использованием анонимных каналов

/* Соединение двух команд с помощью канала в командной строке: pipe команда1 = команда2 */

SECURITY_ATTRIBUTES PipeSA = /* Для наследуемых дескрипторов. */

PROCESS_INFORMATION ProcInfo1, ProcInfo2;

STARTUPINFO StartInfoCh1, StartInfoCh2;

LPTSTR targv = SkipArg(GetCommandLine());

/* Найти символ «=», разделяющий две команды. */

/* Перенаправить стандартный вывод и создать первый процесс. */

CreateProcess(NULL, (LPTSTR)Command1, NULL, NULL, TRUE /* Унаследовать дескрипторы. */, 0, NULL, NULL, &StartInfoCh1, &ProcInfo1);

/* Закрыть дескриптор записи канала, поскольку он больше не нужен, чтобы вторая команда могла обнаружить конец файла. */

/* Повторить операции (симметричным образом) для второго процесса. */

/* Ожидать завершения первого и второго процессов. */

Именованные каналы

Именованные каналы (named pipes) предлагают ряд возможностей, которые делают их полезными в качестве универсального механизма реализации приложений на основе IPC, включая приложения, требующие сетевого доступа к файлам, и клиент-серверные системы[31], хотя для реализации простых вариантов IPC, ориентированных на байтовые потоки, как в предыдущем примере, в котором взаимодействие процессов ограничивается рамками одной системы, анонимных каналов вам будет вполне достаточно. К числу упомянутых возможностей (часть которых обеспечивается дополнительно) относятся следующие:

• Именованные каналы ориентированы на обмен сообщениями, поэтому процесс, выполняющий чтение, может считывать сообщения переменной длины именно в том виде, в каком они были посланы процессом, выполняющим запись.

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

• Допускается существование нескольких независимых экземпляров канала, имеющих одинаковые имена. Например, с единственной серверной системой могут связываться одновременно несколько клиентов, использующих каналы с одним и тем же именем. Каждый клиент может иметь собственный экземпляр именованного канала, и сервер может использовать этот же канал для отправки ответа клиенту.

• Каждая из систем, подключенных к сети, может обратиться к каналу, используя его имя. Взаимодействие посредством именованного канала осуществляется одинаковым образом для процессов, выполняющихся как на одной и той же, так и на разных машинах.

• Имеется несколько вспомогательных и связных функций, упрощающих обслуживание взаимодействия «запрос/ответ» и клиент-серверных соединений.

Как правило, именованные каналы являются более предпочтительными по сравнению с анонимными, хотя программа 11.1 и рис. 11.1 и иллюстрируют ситуацию, в которой анонимные каналы оказываются исключительно полезными. Во всех случаях, когда требуется, чтобы канал связи был двунаправленным, ориентированным на обмен сообщениями или доступным для нескольких клиентских процессов, следует применять именованные каналы. Попытки реализации последующих примеров с использованием анонимных каналов натолкнулись бы на значительные трудности.

Использование именованных каналов

Функция CreateNamedPipe создает первый экземпляр именованного канала и возвращает дескриптор. При вызове этой функции указывается также максимально допустимое количество экземпляров каналов, а следовательно, и количество клиентов, одновременная поддержка которых может быть обеспечена.

Как правило, создающий процесс рассматривается в качестве сервера. Клиентские процессы, которые могут выполняться и на других системах, открывают канал с помощью функции CreateFile.

На рис. 11.2 в иллюстративной форме представлены отношения «клиент/сервер», а также псевдокод, отражающий одну из возможных схем применения именованных каналов. Обратите внимание, что сервер создает множество экземпляров одного и того же канала, каждый из которых обеспечивает поддержку одного клиента. Кроме того, для каждого экземпляра именованного канала сервер создает поток, так что для каждого клиента существует выделенный поток и экземпляр именованного канала. Следовательно, рис. 11.2 показывает, как реализовать модель многопоточного сервера, впервые представленную на рис. 7.1.

Рис. 11.2. Взаимодействие клиентов с сервером через именованные каналы

Создание именованных каналов

Серверами именованных каналов могут быть только системы на основе Windows NT (как обычно, здесь имеются в виду версия 4.0 и последующие); системы на базе Windows 9x могут выступать только в роли клиентов.

Прототип функции CreateNamedPipe представлен ниже.

HANDLE CreateNamedPipe(LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY ATTRIBUTES lpSecurityAttributes)

lpName — указатель на имя канала, который должен иметь следующую форму:

Точка (.) обозначает локальный компьютер; таким образом, создать канал на удаленном компьютере невозможно.

dwOpenMode — указывает один из следующих флагов:

• PIPE_ACCESS_DUPLEX — этот флаг эквивалентен комбинации значений GENERIC_READ и GENERIC_WRITE.

• PIPE_ACCESS_INBOUND — данные могут передаваться только в направлении от клиента к серверу; эквивалентно GENERIC_READ.

• PIPE_ACCESS_OUTBOUND — этот флаг эквивалентен GENERIC_WRITE.

При задании режима могут также указываться значения FILE_FLAG_WRITE_THROUGH (не используется с каналами сообщений) и FILE_FLAG_OVERLAPPED (перекрывающиеся операции рассматриваются в главе 14).

dwPipeMode — имеются три пары взаимоисключающих значений этого параметра. Эти значения указывают, ориентирована ли запись на работу с сообщениями или байтами, ориентировано ли чтение на работу с сообщениями или блоками, и блокируются ли операции чтения.

• PIPE_TYPE_BYTE и PIPE_TYPE_MESSAGE — указывают, соответственно, должны ли данные записываться в канал как поток байтов или как сообщения. Для всех экземпляров каналов с одинаковыми именами следует использовать одно и то же значение.

• PIPE_READMODE_BYTE и PIPE_READMODE_MESSAGE — указывают, соответственно, должны ли данные считываться как поток байтов или как сообщения. Значение PIPE_READMODE_MESSAGE требует использования значения PIPE_TYPE_MESSAGE.

• PIPE_WAIT и PIPE_NOWAIT — определяют, соответственно, будет или не будет блокироваться операция ReadFile. Следует использовать значение PIPE_WAIT, поскольку для обеспечения асинхронного ввода/вывода существуют лучшие способы.

nMaxInstances — определяет количество экземпляров каналов, а следовательно, и количество одновременно поддерживаемых клиентов. Как показано на рис. 11.2, при каждом вызове функции CreateNamedPipe для данного канала должно использоваться одно и то же значение. Чтобы предоставить ОС возможность самостоятельно определить значение этого параметра на основании доступных системных ресурсов, следует указать значение PIPE_UNLIMITED_INSTANCES.

nOutBufferSize и nInBufferSize — позволяют указать размеры (в байтах) выходного и входного буферов именованных каналов. Чтобы использовать размеры буферов по умолчанию, укажите значение 0.

nDefaultTimeOut — длительность интервала ожидания по умолчанию (в миллисекундах) для функции WaitNamedPipe, которая обсуждается в следующем разделе. Эта ситуация, в которой функция, создающая объект, устанавливает интервал ожидания для родственной функции, является уникальной.

В случае ошибки возвращается значение INVALID_HANDLE_VALUE, поскольку дескрипторы каналов аналогичны дескрипторам файлов. При попытке создания именованного канала под управлением Windows 9x, которая не может выступать в качестве сервера именованных каналов, возвращаемым значением будет NULL, что может стать причиной недоразумений.

lpSecurityAttributes — имеет тот же смысл, что и в случае любой функции, создающей объект.

При первом вызове функции CreateNamedPipe происходит создание самого именованного канала, а не просто его экземпляра. Закрытие последнего открытого дескриптора экземпляра именованного канала приводит к уничтожению этого экземпляра (обычно существует по одному дескриптору на каждый экземпляр). Уничтожение последнего экземпляра именованного канала приводит к уничтожению самого канала, в результате чего имя канала становится вновь доступным для повторного использования.

Подключение клиентов именованных каналов

Как показано на рис. 11.2, для подключения клиента к именованному каналу применяется функция CreateFile, при вызове которой указывается имя именованного канала. Часто клиент и сервер выполняются на одном компьютере, и в этом случае для указания имени канала используется следующая форма:

Если сервер находится на другом компьютере, для указания имени канала используется следующая форма:

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

Функции состояния именованных каналов

Предусмотрены две функции, позволяющие получать информацию о состоянии каналов, и еще одна функция, позволяющая устанавливать данные состояния канала. Краткая характеристика этих функций приводится ниже, а одна из этих функций используется в программе 11.2.

• GetNamedPipeHandleState — возвращает для заданного открытого дескриптора информацию относительно того, работает ли канал в блокируемом или неблокируемом режиме, ориентирован ли он на работу с сообщениями или байтами, каково количество экземпляров канала и тому подобное.

• SetNamedPipeHandleState — позволяет программе устанавливать атрибуты состояния. Параметр режима (NpMode) передается не по значению, а по адресу, что может стать причиной недоразумений. Применение этой функции демонстрируется в программе 11.2.

• GetNamedPipeInfo — определяет, принадлежит ли дескриптор экземпляру клиента или сервера, размеры буферов и тому подобное.

Функции подключения именованных каналов

После создания именованного канала сервер может ожидать подключения клиента (осуществляемого с помощью функции CreateFile или функции CallNamedFile, описанной далее в этой главе), используя для этого функцию ConnectNamedPipe, которая является серверной функцией лишь в случае Windows NT:

Bool ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped)

Если параметр lpOverlapped установлен равным NULL, то функция ConnectNamedPipe осуществляет возврат сразу же после установления соединения с клиентом. В случае успешного выполнения функции возвращаемым значением является TRUE. Если же подключение клиента происходит в течение промежутка времени между вызовами сервером функций CreateNamedPipe и ConnectNamed-Pipe, то возвращаемым значением будет FALSE. В этом случае функция GetLastError вернет значение ERROR_PIPE_CONNECTED.

После возврата из функции ConnectNamedPipe сервер может выполнять чтение запросов с помощью функции ReadFile и запись ответов посредством функции WriteFile. Наконец, сервер должен вызвать функцию DisconnectNamedPipe, чтобы освободить дескриптор (экземпляра канала) для соединения с другим возможным клиентом.

Последняя функция, WaitNamedPipe, используется клиентами для синхронизации соединений с сервером. Функция осуществляет успешный возврат, когда на сервере имеется незавершенный вызов функции ConnectNamedPipe, указывающий на наличие доступного экземпляра именованного канала. Используя WaitNamedPipe, клиент имеет возможность убедиться в том, что сервер готов к образованию соединения, после чего может вызвать функцию CreateFile. Вместе с тем, вызов клиентом функции CreateFile может завершиться ошибкой, если в это же время другой клиент открывает экземпляр именованного канала или дескриптор экземпляра закрывается сервером. При этом неудачного завершения вызванной сервером функции ConnectNamedPipe не произойдет. Заметьте, что для функции WaitNamedPipe предусмотрен интервал ожидания, который, если он указан, отменяет значение интервала ожидания, заданного при вызове серверной функции CreateNamedPipe.

Подключение клиентов и серверов именованных каналов

Операции по подключению клиентов и серверов к именованным каналам выполняются в описанном ниже порядке. Сначала мы рассмотрим последовательность операций, выполняемых сервером, при помощи которых сервер создает соединение с клиентом, взаимодействует с клиентом до тех пор, пока тот не разорвет соединение (вынуждая функцию ReadFile вернуть значение FALSE), разрывает соединение на стороне сервера, а затем образует соединение с другим клиентом:

/* Последовательность операций при создании соединения с использованием именованного канала для сервера. */

while (… /* Цикл продолжается вплоть до завершения работы сервера.*/) <

Перейдем к рассмотрению последовательности операций, выполняемых клиентом, в которой клиент прекращает выполнение после завершения работы, давая возможность подключиться к тому же экземпляру именованного канала другому клиенту. Как показано ниже, клиент может соединиться с сервером в сети, если ему известно сетевое имя сервера (ServerName):

/* Последовательность операций при создании соединения с использованием именованного канала для клиента. */

while (…/*Цикл выполняется до тех пор, пока не прекратятся запросы.*/ <

CloseHandle (hNp); /* Разорвать соединение с сервером. */

Обратите внимание, что клиент и сервер состязаются за ресурсы. Прежде всего, клиентский вызов функции WaitNamedPipe завершится ошибкой, если именованный канал к этому моменту еще не был создан сервером; для краткости тестирование успешности выполнения в нашем примере опущено, однако оно включено в примеры программ, доступные на Web-сайте. Далее, в редких случаях вызов CreateFile может быть выполнен еще до того, как сервер вызовет функцию ConnectNamedPipe. В этом случае функция ConnectNamedPipe вернет серверу значение FALSE, однако взаимодействия посредством именованного канала по-прежнему будет функционировать надлежащим образом.

Экземпляр именованного канала является глобальным ресурсом, поэтому, когда клиент разрывает соединение с сервером, к нему может подключиться другой клиент.

Функции транзакций именованных каналов

На рис. 11.2 показана типичная конфигурация клиента, в которой клиент выполняет следующие операции:

• Открывает экземпляр канала, создавая долговременное соединение с сервером и занимая экземпляр канала.

• Периодически посылает запросы и ожидает получения ответов.

Встречающуюся здесь последовательность вызовов функций WriteFile и ReadFile можно рассматривать как единую клиентскую транзакцию, и Windows предоставляет соответствующую функцию для каналов сообщений:

BOOL TransactNamedPipe(HANDLE hNamedPipe, LPVOID lpWriteBuf, DWORD cbWriteBuf, LPVOID lpReadBuf, DWORD cbReadBuf, LPDWORD lpcbRead, LPOVERLAPPED lpOverlapped)

Смысл всех параметров здесь должен быть ясен, поскольку данная функция сочетает в себе функции WriteFile и ReadFile, применяемые к дескриптору именованного канала. Указываются как выходной, так и входной буфер, а разыменованный указатель lpcbRead предоставляет размер сообщения. Перекрывающиеся операции (глава 14) возможны, однако в более типичных случаях функция ожидает ответа.

Функция TransactNamedPipe удобна в использовании, однако, как показывает рис. 11.2, она требует создания постоянного соединения, что ограничивает число возможных клиентов[32].

Ниже приводится прототип второй клиентской вспомогательной функции.

BOOL CallNamedPipe(LPCTSTR lpPipeName, LPVOID lpWriteBuf, DWORD cbWriteBuf, LPVOID lpReadBuf, DWORD cbReadBuf, LPDWORD lpcbRead, DWORD dwTimeOut)

Функция CallNamedPipe не требует образования постоянного соединения; вместо этого она создает временное соединение, объединяя в себе выполнение следующей последовательности операций:

Преимуществом такого способа является лучшее использование канала за счет снижения накладных расходов системных ресурсов на один запрос.

Смысл параметров этой функции тот же, что и в случае функции TransactNamedPipe, если не считать того, что вместо дескриптора для указания канала используется его имя. Функция CallNamedPipe выполняется синхронном режиме (отсутствует структура OVERLAPPED). Указываемая при ее вызове длительность периода ожидания (dwTimeOut) (в миллисекундах) относится к соединению, а не транзакции. Параметр dwTimeOut имеет три специальных значения:

• NMPWAIT_USE_DEFAULT_WAIT, которое приводит к использованию интервала ожидания по умолчанию, заданного в вызове функции CreateNamedPipe.

Определение наличия сообщений в именованных каналах

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

BOOL PeekNamedPipe(HANDLE hPipe, LPVOID lpBuffer, DWORD cbBuffer, LPDWORD lpcbRead, LPDWORD lpcbAvail, LPDWORD lpcbMessage)

Функция PeekNamedPipe обеспечивает считывание любого байта или сообщения из канала без их удаления, но ее невозможно блокировать, и она осуществляет возврат сразу же по завершении выполнения.

Чтобы определить, имеются ли в канале данные, необходимо проверить значение *lpcbAvail; если данные в канале присутствуют, оно должно быть больше 0. В этом случае параметры lpBuffer и lpcbRead могут иметь значения NULL. Если же буфер определен параметрами lpBuffer и cbBuffer, то значение *lpcbMessage укажет вам, остается ли еще некоторое количество байтов сообщений, которые не умещаются в буфере, что позволяет распределять буфер большего размера, прежде чем осуществлять чтение из именованного канала. Для канала, работающего в режиме считывания байтов, это значение равно 0.

Следует помнить, что функция PeekNamedPipe осуществляет чтение, не уничтожая данные, и поэтому для удаления сообщений или байтовых данных из канала требуется последующее применение функции ReadFile.

Каналы UNIX FIFO аналогичны именованным каналам и, таким образом, обеспечивают взаимодействие не связанных между собой процессов. Однако по сравнению с именованными каналами Windows их возможности являются несколько ограниченными:

• Каналы FIFO являются полудуплексными.

• Каналы FIFO действуют только в пределах одного компьютера.

• Каналы FIFO ориентированы на работу с байтами, поэтому в клиент-серверных приложениях проще всего использовать записи фиксированной длины. Тем не менее, отдельные операции чтения и записи являются атомарными.

Сервер, на котором применяется это средство, должен использовать для каждого ответа клиентам отдельный канал FIFO, хотя все клиенты могут посылать запросы по одному и тому же известному каналу. В соответствии с общепринятой практикой клиенты включают имя канала FIFO в запрос соединения.

Функция UNIX mkfifo является ограниченной версией функции CreateNamedFile.

Если клиенты и сервер должны находиться в сети, используйте сокеты или аналогичный механизм транспортировки сетевых сообщений. Сокеты работают в дуплексном режиме, однако требуют использования отдельного соединения для каждого клиента.

Пример: клиент-серверный процессор командной строки

Теперь мы располагаем всем необходимым для построения клиент-серверной системы, работающей с запросами и ответами. В данном примере будет представлен сервер командной строки, выполняющий команду по требованию клиента. Система характеризуется следующими особенностями:

• С сервером могут взаимодействовать несколько клиентов.

• Клиенты могут находиться на различных системах в сети, хотя допускается и их расположение на компьютере сервера.

• Сервер является многопоточным, причем каждому именованному каналу назначается отдельный поток. Это означает, что существует пул потоков (thread pool), в который входят рабочие потоки, готовые к использованию подключающимися клиентами. Рабочие потоки предоставляются клиентам посредством экземпляра именованного канала, который система выделяет клиенту.

• Отдельные потоки сервера в каждый момент времени обрабатывают один запрос, что упрощает управление параллелизмом их выполнения. Каждый из потоков самостоятельно обрабатывает свои запросы. Тем не менее, требуется предпринимать обычные меры предосторожности на тот случай, если несколько различных потоков сервера пытаются получить доступ к одному и тому же файлу или иному ресурсу.

В программе 11.2 представлен однопоточной клиент, а в программе 11.3 — его сервер. Сервер соответствует модели, представленной на рисунках 7.1 и 11.2. Запросом клиента является обычная командная строка. Ответом сервера является результирующий вывод, который посылается в виде нескольких сообщений. Кроме того, в программе используется находящийся на Web-сайте заголовочный файл ClntSrvr.h, в котором определены структуры данных запроса и ответа, а также имена каналов клиента и сервера.

В программе 11.2 клиент вызывает функцию LocateServer, которая находит имя канала сервера. Функция LocateServer использует почтовый ящик (mailslot), описанный в одном из последующих разделов и представленный в программе 11.5.

В объявлениях записей имеются поля длины, тип которых определен как DWORD32; это сделано для того, чтобы программы, получая возможность их последующего перенесения на платформу Win64, могли взаимодействовать с серверами и клиентами, выполняющимися под управлением любой системы Windows.

Программа 11.2. clientNP: клиент, ориентированный на соединение посредством именованного канала

/* Глава 11. Клиент-серверная система. ВЕРСИЯ КЛИЕНТА.

clientNP — клиент, ориентированный на установку соединения. */

/* Выполнить командную строку (на сервере); отобразить ответ. */

/* Клиент создает долговременное соединение с сервером (захватывая */

/* экземпляр канала) и выводит приглашение пользователю для ввода команд.*/

#include «ClntSrvr.h» /* Определяет структуры записей запроса и ответа. */

HANDLE hNamedPipe = INVALID_HANDLE_VALUE;

RESPONSE Response; /* См. файл ClntSrvr.h. */

DWORD nRead, nWrite, NpMode = PIPE_READMODE_MESSAGE | PIPE_WAIT;

/* Ожидать появления экземпляра именованного канала и «вступить в борьбу» за право его открытия. */

while (INVALID_HANDLE_VALUE == hNamedPipe) <

hNamedPipe = CreateFile(ServerPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

/* Задать блокирование дескриптора именованного канала; режим сообщений.*/

/* Вывести приглашение пользователю для ввода команд. Завершить выполнение по получении команды «$quit.» */

while (ConsolePrompt(PromptMsg, Request.Record, MAX_RQRS_LEN, TRUE) && (_tcscmp(Request.Record, QuitMsg) != 0)) <

/* Считать каждый ответ и направить его на стандартный вывод.

Response.Status = 0 означает «конец ответного сообщения.» */

_tprintf(_T(«Получена команда завершения работы. Соединение разрывается.»));

Программа 11.3 — это серверная программа, включающая функцию потока сервера, которая обрабатывает запросы, генерируемые с помощью программы 11.2. Кроме того, сервер создает «широковещательный серверный поток» («server broadcast thread») (см. программу 11.4), который используется для широковещательной рассылки имени своего канала всем клиентам, желающим подключиться, посредством почтового ящика. В программе 11.2 вызывается функция LocateServer, представленная в программе 11.5, которая считывает информацию, отправленную данным процессом. Почтовые ящики описываются далее в настоящей главе.

Хотя соответствующий код и не включен в программу 11.4, в ней предусмотрена возможность защиты сервером (представлен на Web-сайте) своего именованного канала с целью предотвращения доступа к нему клиентов, не имеющих должных полномочий. Вопросы безопасности объектов рассматриваются в главе 15, где будет также показано, как использовать указанную возможность.

Программа 11.3. serverNP: многопоточный сервер именованного канала

/* Многопоточный сервер командной строки. Версия на основе именованных каналов. */

#include «ClntSrvr.h» /* Определения сообщений запроса и ответа. */

HANDLE hNamedPipe; /* Экземпляр именованного канала. */

TCHAR TmpFileName[MAX_PATH]; /* Имя временного файла. */

volatile static BOOL ShutDown = FALSE;

static DWORD WINAPI Server(LPTHREAD_ARG);

static DWORD WINAPI Connect(LPTHREAD_ARG);

static DWORD WINAPI ServerBroadcast(LPLONG);

static TCHAR ShutRqst[] = _T(«$ShutDownServer»);

/* Определение MAX_CLIENTS содержится в файле ClntSrvr.h. */

HANDLE hNp, hMonitor, hSrvrThread[MAXCLIENTS];

LPSECURITY_ATTRIBUTES pNPSA = NULL;

/* Обработчик управляющих сигналов консоли, используемый для остановки сервера. */

/* Периодически создавать имя широковещательного канала потока. */

hMonitor = (HANDLE)_beginthreadex(NULL, 0, ServerBroadcast, NULL, 0, &MonitorId);

/* Создать экземпляр канала и временный файл для каждого серверного потока. */

hTmpFile = CreateFile(pThArg->TmpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &TempSA, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);

/* Создать поток соединения; ждать его завершения. */

hConTh = (HANDLE)_beginthreadex(NULL, 0, Connect, pThArg, 0, &ConThId);

/* Ожидание соединения с клиентом и проверка флага завершения работы.*/

if (ShutDown) continue; /*Флаг может быть установлен любым потоком.*/

while (!ShutDown && ReadFile(hNamedPipe, &Request, RQ_SIZE, &nXfer, NULL)) <

/* Получать новые команды до отсоединения клиента. */

ShutDown = ShutDown || (_tcscmp(Request.Record, ShutRqst) == 0);

if (ShutDown) continue; /* Проверяется на каждой итерации. */

/* Создать процесс для выполнения команды. */

CreateProcess(NULL, Request.Record, NULL, NULL, TRUE, /* Унаследовать дескрипторы. */

/* Отвечать по одной строке за один раз. Здесь удобно использовать функции библиотеки С для работы со строками. */

/* Уничтожить содержимое временного файла. */

SetFilePointer(hTmpFile, 0, NULL, FILE_BEGIN);

/* Конец основного командного цикла. Получить следующую команду. */

/* Принудительно завершить выполнение потока, если он все еще активен.*/

hClient = CreateFile(SERVER_PIPE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN EXISTING, FILE ATTRIBUTE NORMAL, NULL);

if (hClient != INVALID_HANDLE_VALUE) CloseHandle (hClient);

WaitForSingleObject (hConTh, INFINITE);

/* Клиент отсоединился или имеется запрос останова. */

/* Конец командного цикла. Освободить ресурсы; выйти из потока. */

if (hTmpFile != INVALID_HANDLE_VALUE) CloseHandle(hTmpFile);

_tprintf(_T(«Выход из потока номер %d\n»), pThArg->ThreadNo);

static DWORD WINAPI Connect(LPTHREAD_ARG pThArg) <

/* Поток соединения разрешает серверу опрос флага ShutDown. */

BOOL WINAPI Handler(DWORD CtrlEvent) <

Комментарии по поводу клиент-серверного процессора командной строки

Данное решение характеризуется рядом особенностей и ограничений, которые будут обсуждаться в последующих главах.

• Соединяться с сервером и выполнять параллельные запросы могут сразу несколько серверов; каждому клиенту назначается серверный (или рабочий) поток, выделяемый из пула потоков.

• Сервер и клиенты могут выполняться либо в ответ на отдельные подсказки командной строки, либо под управлением программы JobShell (программа 6.3).

• Если во время попыток клиента соединиться с сервером все экземпляры именованного канала оказываются задействованными, то новый клиент будет находиться в состоянии ожидания до тех пор, пока другой клиент не разорвет соединение в ответ на получение команды $Quit, тем самым делая его доступным для ожидающего клиента. Возможны ситуации, когда сразу несколько новых клиентов будут одновременно пытаться создать соединение с сервером, соревнуясь между собой за право открытия доступного экземпляра; потоки, проигравшие в этой конкурентной борьбе, будут вынуждены вновь перейти в состояние ожидания.

• Каждый серверный поток выполняет синхронные операции ввода/вывода, но одни из этих потоков могут обрабатывать запросы, в то время как другие — ожидать соединения или поступления клиентских запросов.

• С учетом ограничений, свойственных именованным каналам, о чем говорилось ранее в этой главе, расширение программы на случай сетевых клиентов не составляет труда. Для этого достаточно заменить имена каналов в заголовочном файле или добавить параметр, указывающий имя сервера в командной строке клиента.

• Каждый рабочий поток сервера создает простой поток, осуществляющий соединение, который вызывает функцию ConnectNamedPipe и завершает выполнение сразу же после подключения клиента. Это позволяет организовать ожидание дескриптора потока соединения рабочим потоком с использованием конечного интервала ожидания и периодическое тестирование глобального флага завершения работы (ShutDown). Если бы рабочие потоки блокировались при выполнении функции ConnectNamedPipe, они не могли бы тестировать этот флаг, и сервер не мог бы завершить работу. По этой причине поток сервера осуществляет вызов CreateFile, используя дескриптор именованного канала, чтобы заставить поток соединения возобновиться и завершить выполнение. Альтернативным вариантом было бы использование асинхронного ввода/вывода (глава 14), что дало бы возможность связать событие с вызовом функции ConnectNamedPipe. Другие возможные варианты реализации и дополнительная информация предоставляются в комментариях к исходному тексту программы, размещенному на Web-сайте книги. Без этого решения потоки соединения могли бы никогда не завершить работу самостоятельно, что привело бы к утечке ресурсов в DLL. Этот вопрос обсуждается в главе 12.

• Существует ряд благоприятных предпосылок для усовершенствования данной системы. Например, можно предусмотреть опцию выполнения внутрипроцессного сервера (in-process server), используя библиотеку DLL, которая реализует некоторые из команд. Это усовершенствование вводится в программу в главе 12.

• Количество серверных потоков ограничивается при вызове функции WaitForMultipleObjects в основном потоке. Хотя это ограничение легко преодолимо, в данном случае система не обладает истинной масштабируемостью; как было показано в главе 10, чрезмерное увеличение количества потоков может оказать отрицательное влияние на производительность. В главе 14 для решения этой проблемы используются порты асинхронного ввода/вывода.

Почтовые ящики

Как и именованные каналы, почтовые ящики (mailslots) Windows снабжаются именами, которые могут быть использованы для обеспечения взаимодействия между независимыми каналами. Почтовые ящики представляют собой широковещательный механизм, основанный на дейтаграммах (описаны в главе 12), и ведут себя иначе по сравнению с именованными каналами, что делает их весьма полезными в ряде ограниченных ситуаций, которые, тем не менее, представляют большой интерес. Из наиболее важных свойств почтовых ящиков можно отметить следующие:

• Почтовые ящики являются однонаправленными.

• С одним почтовым ящиком могут быть связаны несколько записывающих программ (writers) и несколько считывающих программ (readers), но они часто связаны между собой отношениями «один ко многим» в той или иной форме.

• Записывающей программе (клиенту) не известно достоверно, все ли, только некоторые или какая-то одна из программ считывания (сервер) получили сообщение.

• Почтовые ящики могут находиться в любом месте сети.

• Размер сообщений ограничен.

Использование почтовых ящиков требует выполнения следующих операций:

• Каждый сервер создает дескриптор почтового ящика с помощью функции CreateMailSlot.

• После этого сервер ожидает получения почтового сообщения, используя функцию ReadFile.

• Клиент, обладающий только правами записи, должен открыть почтовый ящик, вызвав функцию CreateFile, и записать сообщения, используя функцию WriteFile. В случае отсутствия ожидающих программ считывания попытка открытия почтового ящика завершится ошибкой (наподобие «имя не найдено»).

Сообщение клиента может быть прочитано всеми серверами; все серверы получают одно и то же сообщение.

Существует еще одна возможность. В вызове функции CreateFile клиент может указать имя почтового ящика в следующем виде:

При этом символ звездочки (*) действует в качестве группового символа (wildcard), и клиент может обнаружить любой сервер в пределах имени домена — группы систем, объединенных общим именем, которое назначается администратором сети.

Использование почтовых ящиков

Рассмотренный перед этим клиент-серверный процессор командной строки предполагает несколько возможных способов его использования. Рассмотрим один из сценариев, в котором решается задача обнаружения сервера в только что упомянутой клиент-серверной системе (программы 11.2 и 11.3).

Сервер приложения (application server), действуя в качестве почтового клиента (mailslot client), периодически осуществляет широковещательную рассылку своего имени и имени именованного канала. Любой клиент приложения (application client), которому требуется найти сервер, может получить это имя, действуя в качестве сервера почтовых ящиков (mailslot server). Аналогичным образом сервер командной строки может периодически осуществлять широковещательную рассылку своего состояния, включая информацию о коэффициенте использования, клиентам. Это соответствует ситуации, в которой имеется одна записывающая программа (почтовый клиент) и несколько считывающих программ (почтовых серверов). Если бы почтовых клиентов (то есть серверов приложения) было несколько, то ситуация описывалась бы отношением типа «многие ко многим».

Возможен и другой вариант, когда одна считывающая программа получает сообщения от многочисленных записывающих программ, которые, например, предоставляют информацию о своем состоянии. Этот вариант, соответствующий, например, электронной доске объявлений, оправдывает использование термина почтовый ящик. Оба описанных варианта использования — широковещательная рассылка имени и информации о состоянии — могут быть объединены, чтобы клиент мог выбирать наиболее подходящий сервер.

Обмен ролями терминов клиент и сервер в данном контексте может несколько сбивать с толку, однако заметьте, что сервер именованного канала и почтовый сервер выполняют вызовы функций CreateNamedPipe (или CreateMailSlot), тогда как клиент (именованного канала или почтового ящика) создает соединение, используя функцию CreateFile. Кроме того, в обоих случаях первый вызов функции WriteFile выполняется клиентом, а первый вызов функции ReadFile выполняется сервером.

Использование почтовых ящиков в соответствии с первым из описанных возможных вариантов иллюстрируется на рис. 11.3.

Создание и открытие почтового ящика

Для создания почтового ящика и получения дескриптора, который можно будет использовать в операциях ReadFile, почтовые серверы (программы считывания) вызывают функцию CreateMailslot. На одном компьютере может находиться только один почтовый ящик с данным именем, но один и тот же почтовый ящик может использоваться несколькими системами в сети, что обеспечивает возможность работы с ним нескольких программ считывания.

Рис. 11.3. Использование клиентами почтового ящика для обнаружения сервера

HANDLE CreateMailslot(LPCTSTR lpName, DWORD cbMaxMsg, DWORD dwReadTimeout, LPSECURITY_ATTRIBUTES lpsa)

lpName — указатель на строку с именем почтового ящика, которое должно иметь следующий вид:

Имя должно быть уникальным. Точка (.) указывает на то, что почтовый ящик создается на локальном компьютере.

cbMaxMsg — максимальный размер сообщения (в байтах), которые может записывать клиент. Значению 0 соответствует отсутствие ограничений.

dwReadTimeOut — длительность интервала ожидания (в миллисекундах) для операции чтения. Значению 0 соответствует немедленный возврат, а значению MAILSLOT_WAIT_FOREVER — неопределенный период ожидания (который может длиться сколь угодно долго).

Во время открытия почтового ящика с помощью функции CreateFile клиент (записывающая программа) может указывать его имя в следующем виде:

• \\ .\mailslot\ [путь]имя — определяет локальный почтовый ящик. Примечание. В Windows 95 длина имени ограничена 11 символами.

• \\имя_компьютера\mailslot\[путь]имя — определяет почтовый ящик, расположенный на компьютере с заданным именем.

• \\имя_домена\mailslot\[путь]имя — определяет все почтовые ящики с данным именем, расположенные на компьютерах, принадлежащих данному домену. В этом случае максимальный размер сообщения составляет 424 байта.

• \\*\mailslot\[путь]имя — определяет все почтовые ящики с данным именем, расположенные на компьютерах, принадлежащих главному домену системы. В этом случае максимальный размер сообщения составляет 424 байта.

Наконец, клиент должен указывать флаг FILE_SHARE_READ. Функции GetMailslotInfo и SetMailslotInfо похожи на свои аналоги, работающие с именованными каналами.

Средства, сопоставимые с почтовыми ящиками, в UNIX отсутствуют. Однако для этой цели могут быть использованы широковещательные (broadcast) или групповые (multicast) дейтаграммы протокола TCP/IP.

Создание, подключение и именование каналов и почтовых ящиков

В табл. 11.1 сведены все допустимые формы имен каналов, которые могут использоваться клиентами и серверами приложения. Здесь же перечислены все функции, которые следует использовать для создания именованных каналов и соединения с ними.

Аналогичная информация для почтовых ящиков приведена в табл. 11.2. Вспомните, что почтовый клиент (или сервер) не обязательно должен выполняться тем же процессом или даже на той же системе, что и клиент (или сервер) приложения.

Таблица 11.1. Именованные каналы: создание, подключение и именование

Клиент приложения Сервер приложения
Дескриптор именованного канала или соединение CreateFile CallNamedPipe TransactNamedPipe CreateNamedPipe
Имя канала \\.\имя канала (канал является локальным) \\имя системы\имя канала (канал является локальным или удаленным) \\.\имя канала (канал создается локальным)

Таблица 11.2. Почтовые ящики: создание, подключение и именование

Почтовый клиент Почтовый сервер
Дескриптор почтового ящика CreateFile CreateMailslot
Имя почтового ящика \\.\имя почтового ящика (почтовый ящик является локальным) \\имя системы\имя почтового ящика (почтовый ящик располагается на указанной удаленной системе) \\*\имя почтового ящика (все почтовые ящики, имеющие одно и то же указанное имя) \\.\имя почтового ящика (почтовый ящик создается локальным)

Пример: сервер, обнаруживаемый клиентами

Программа 11.4 представляет функцию потока, которую сервер командной строки (программа 11.3), выступающий в роли почтового клиента, использует для широковещательной рассылки имени своего канала ожидающим клиентам. Возможно существование нескольких серверов с различными характеристиками и именами каналов, и клиенты получают их имена, используя почтовый ящик с известным именем. Эта функция запускается как поток программой 11.3.

На практике многие клиент-серверные системы инвертируют используемую здесь логику поиска. Суть альтернативного варианта заключается в том, что клиент приложения действует и как почтовый клиент, осуществляя широковещательную рассылку сообщений, требующих, чтобы сервер ответил с использованием указанного именованного канала (имя канала определяется клиентом и включается в сообщение). Затем сервер приложения, действующий в качестве почтового сервера, считывает запрос и создает соединение с использованием указанного именованного канала.

static DWORD WINAPI ServerBroadcast(LPLONG pNull) <

/*Открыть почтовый ящик для записывающей программы почтового «клиента»*/

/* Ждать, пока другой клиент не откроет почтовый ящик. */

hMsFile = CreateFile(MS_CLTNAME, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN EXISTING, FILE ATTRIBUTE NORMAL, NULL);

if (hMsFile == INVALID_HANDLE_VALUE) continue;

/* Отправить сообщение в почтовый ящик. */

_tprintf(_T(«Закрытие контролирующего потока.\n»));

В программе 11.5 представлена функция, которая вызывается клиентом (см. программу 11.2) для обнаружения сервера.

Программа 11.5. LocSrvr: почтовый сервер

/* Найти сервер путем считывания информации из почтового ящика, используемого для широковещательной рассылки имен серверов. */

#include «ClntSrvr.h» /* Определяет имя почтового ящика. */

BOOL LocateServer(LPTSTR pPipeName) <

MsFile = CreateMailslot(MS_SRVNAME, 0, CS_TIMEOUT, NULL);

Комментарии по поводу многопоточных моделей

Для описания методов проектирования многопоточных программ используются такие термины, как пул потоков (thread pool), симметричные потоки (symmetric threads) и асимметричная потоковая организация программ (asymmetric threading), а мы при создании примеров использовали модель «хозяин/рабочий», именованные каналы и другие классические модели организации многопоточного выполнения программ.

В этом разделе дано краткое объяснение некоторых полезных описательных терминов, которые являются неотъемлемой частью объектно-ориентированной технологии, основанной на разработанной компанией Microsoft модели компонентных объектов (Component Object Model, СОМ; см. [3]): однопоточная модель (single threading), модель апартаментных потоков (apartment model) и модель свободных потоков (free threading). В СОМ эти модели реализуются за счет использования функций Windows, предназначенных для управления потоками и синхронизации их выполнения. Каждая из перечисленных моделей обладает отличными от других характеристиками производительности и предъявляет свои требования к синхронизации.

• Пул потоков — это совокупность потоков, доступных для использования по мере необходимости. С помощью рис. 7.1 и программы 11.3 иллюстрируется пул потоков, которые могут назначаться новым клиентам, подключающимся к соответствующему именованному каналу. При отсоединении клиента поток возвращается в пул.

• Потоковая модель является симметричной, если группа потоков выполняют одну и ту же задачу с использованием одной и той же функции потока. Симметричные потоки используются в программе grepMT (программа 7.1): все потоки выполняют один и тот же код поиска шаблона. Обратите внимание, что эти потоки не образуют пула; каждый из них создается для выполнения определенной задачи и завершается сразу же после того, как задача выполнена. Пул симметричных потоков создается в программе 11.3.

• Потоковая модель является асимметричной, если различные потоки выполняют различные задачи с использованием различных функций потока. Так, функция потока широковещательной рассылки сообщений, представленная на рис. 7.1 и реализованная в программе 11.4, и функция сервера соответствуют модели асимметричных потоков.

• В соответствии с терминологией СОМ объект является однопоточным, если доступ к нему может получать только один поток. Это означает, что доступ к такому объекту сериализуется. В случае сервера базы данных таким объектом будет сама база данных. В примерах, приведенных в настоящей главе, многопоточная модель используется для организации доступа к объекту, в качестве которого могут рассматриваться программы и файлы, расположенные на компьютере сервера.

• В соответствии с терминологией СОМ об апартаментной модели следует говорить тогда, когда для каждого экземпляра объекта назначается отдельный поток. Так, отдельные потоки могут назначаться для осуществления доступа к разным базам данных или частям базы данных. Доступ к объекту сериализуется с помощью единственного потока.

• Объект, соответствующий модели свободных потоков, имеет поток, который обычно назначается ему из пула потоков по запросу. Обсуждавшийся в настоящей главе сервер можно считать сервером со свободными потоками, если соединение рассматривать как запрос. Аналогично, если сервер базы данных поддерживается потоками, то можно говорить о том, что база данных соответствует модели свободных потоков.

Некоторые программы, например sortMT (программа 7.2), в рамки ни одной из перечисленных моделей точно не укладываются. Вспомните также, что нами уже использовались и другие модели, а именно, модель «хозяин/рабочий», именованные каналы и клиент-серверные модели, применение которых является общепринятым, однако не находит отражения в моделях Microsoft.

Применение указанных моделей многопоточного программирования оказывается уместным и в главе 12, в которой вводятся внутрипроцессные серверы, и компания Microsoft использует соответствующие термины в некоторой части своей документации. Не забывайте о том, что эти термины определены применительно к СОМ-объектам; предыдущее обсуждение показало, как использовать их в более широком контексте. СОМ — это слишком большой и сложный предмет, чтобы мы могли полностью описать его в данной книге. В списке литературы, приведены некоторые ссылки, которыми вы можете воспользоваться для получения более подробных сведений по этому вопросу.

Резюме

Каналы и почтовые ящики Windows, доступ к которым осуществляется с помощью операций файлового ввода/вывода, обеспечивают поточное межпроцессное и сетевое взаимодействие. В примерах продемонстрировано, как организовать передачу данных из одного процесса в другой при помощи каналов и как построить простую многопоточную клиент-серверную систему. Кроме того, каналы обеспечивают дополнительную возможность синхронизации потоков, поскольку считывающий поток блокируется до тех пор, пока другой поток не выполнит запись в канал.

В следующих главах

В главе 12 для осуществления межпроцессного и сетевого взаимодействия вместо оригинальных механизмов Windows привлечены стандартные механизмы. Прежний вариант клиент-серверной системы переработан с использованием стандартных методов и дополнительно усовершенствован за счет некоторого улучшения серверной части.

Упражнения

11.1. Проведите эксперименты с целью проверки справедливости приведенных ранее утверждений относительно повышения производительности, обеспечиваемого использованием функции TransactNamedPipe. Для этого вам придется внести в существующий код сервера некоторые изменения. Кроме того, сопоставьте полученные результаты с теми, к которым приводит текущая реализация.

11.2. Воспользуйтесь программой JobShell из главы 6 для запуска сервера и нескольких клиентов, причем каждый из клиентов должен создаваться в режиме «отсоединения». Для завершения работы остановите сервер путем посылки управляющего сигнала консоли с помощью команды kill. Имеются ли у вас какие-либо соображения по поводу улучшения логики закрытия программы serverNP, чтобы подключенный серверный поток мог проверять флаг завершения работы, будучи блокированным в ожидании запроса клиента? Подсказка. Создайте считывающий поток, аналогичный потоку соединения.

11.3. Усовершенствуйте сервер таким образом, чтобы имя его канала задавалось в виде аргумента в командной строке. Организуйте несколько серверных процессов с различными именами каналов, используя программу управления заданиями из главы 6. Убедитесь в том, что одновременно несколько клиентов получают доступ к этой многопроцессной серверной системе.

11.4. Запустите клиент и сервер на различных системах, чтобы проверить их работу в условиях сети. Измените программу SrvrBcst (программа 11.4) таким образом, чтобы она включала имя компьютера сервера в имя канала. Кроме того, видоизмените имя почтового ящика, используемого в программе 11.4.

11.5. Модифицируйте сервер таким образом, чтобы можно было измерить степень его загрузки (иными словами, чтобы можно было определить, какая доля использованного времени приходится на сервер). Организуйте накопление данных, касающихся производительности, и передачу этой информации клиенту в ответ на его запрос. Для этой цели можно использовать поле Request.Command.

11.6. Усовершенствуйте программы, предназначенные для обнаружения сервера, таким образом, чтобы поиск сервера клиентом осуществлялся с наименьшей степенью загрузки.

11.7. Усовершенствуйте сервер таким образом, чтобы запрос включал в себя рабочий каталог. Сервер должен устанавливать свой рабочий каталог, выполнять команду, а затем восстанавливать рабочий каталог до прежнего значения. Предостережение. Серверный поток не должен устанавливать рабочий каталог процесса; вместо этого каждый поток должен поддерживать строку, представляющую его рабочий каталог, и присоединять эту строку в начало соответствующих путей доступа.

11.8. Программа serverNP спроектирована таким образом, что она выполняется как сервер в течение неопределенного времени, давая клиентам возможность подключаться, получать услуги и разрывать соединение. Очень важно, чтобы при отключении клиента сервер освобождал все соответствующие ресурсы, например, память, дескрипторы файлов или потоков. Если это не делается, то в результате утечки ресурсов системные ресурсы, в конце концов, исчерпаются, что приведет аварийному завершению работы сервера с возможным предшествующим ухудшением показателей производительности. Тщательно проверьте код программы serverNP, чтобы убедиться в отсутствии утечки ресурсов, и в случае необходимости внесите необходимые исправления. (Просьба информировать автора обо всех обнаруженных ошибках, используя указанный в предисловии адрес электронной почты.) Примечание. Утечка ресурсов является весьма распространенным серьезным дефектом многих промышленных систем. Никакие попытки вывода изделия на уровень «промышленных стандартов» нельзя считать успешными, если указанной проблеме не было уделено должного внимания.

11.9. Расширенное упражнение. Объекты синхронизации могут использоваться для синхронизации потоков, выполняющихся в различных процессах на одной и той же машине, но с их помощью нельзя синхронизировать потоки процессов, которые выполняются на разных машинах. Используя именованные каналы и почтовые ящики, создайте эмулированные мьютексы, события и семафоры, чтобы преодолеть это ограничение.

Примечания:

Замечания, сделанные в адрес UNIX, в равной степени относятся также к Linux и некоторым другим системам, поддерживающим POSIX API.

Как показано в главе 10, в упражнении с семафором (упражнение 10.11), системные службы Windows предоставляют возможность организации взаимодействия между процессами также посредством отображаемых файлов. Дополнительные механизмы IPC включают файлы, сокеты, удаленные вызовы процедур, СОМ и отправку сообщений через почтовые ящики. Сокеты рассматриваются в главе 12.

Это утверждение нуждается в дополнительных разъяснениях. Для большинства сетевых приложений и высокоуровневых протоколов (http, ftp и так далее) более предпочтительным является интерфейс Windows Sockets API, особенно в тех случаях, когда требуется обеспечить межплатформенное взаимодействие с системами, отличными от Windows, на основе протокола TCP/IP. Многие разработчики предпочитают ограничивать использование именованных каналов лишь случаями IPC в пределах обособленной системы или в сетях Windows.

Заметьте, что функция TransactNamedPipe не только предлагает более удобный способ использования пары функций WriteFile и ReadFile, но и обеспечивает определенные преимущества в плане производительности. Один из экспериментов продемонстрировал повышение пропускной способности канала в интервале от 57% (небольшие сообщения) до 24% (крупные сообщения).

Читайте также:  Максимальный объем оперативной памяти windows 10 домашняя
Оцените статью