Использование почтовых ящиков для связи между процессами - Создание почтового ящика

ОГЛАВЛЕНИЕ

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

Выполняется путем вызова API CreateMailslot(), передачи ему имени почтового ящика в формате UNC и ряда других параметров. Вы можете создать почтовый ящик только на локальном компьютере, поэтому серверная часть пути UNC должна разрешаться в локальный компьютер; то есть, это должно быть имя локального компьютера или точка '.' - использовать '.' проще всего. Другие параметры, по порядку: максимальный размер сообщения, которое может быть отправлено почтовому ящику, a также время ожидания, определяющее, как долго читатель почтового ящика будет ждать сообщение, и дескриптор защиты, определяющий, может ли дескриптор, возвращенный API CreateMailslot(), быть унаследован дочерним процессом.

Подключение к почтовому ящику

Выполняется просто. Вы используете API CreateFile(), указывая имя почтового ящика в формате UNC. Будьте осторожны с режимами совместного доступа при открытии почтового ящика, если вы реализуете модель много писателей/один читатель. Если писатель почтового ящика открывает почтовый ящик без указания FILE_SHARE_WRITE в качестве режима совместного доступа, он не позволит ни одному следующему писателю писать в почтовый ящик. Неприятный момент в реализации API заключается в том, что последующие вызовы CreateFile() удаются при условии, что они получают обратно действительный дескриптор почтового ящика, но все записанное с помощью этого дескриптора теряется.

Что делать с дескриптором почтового ящика после его получения?

Если вы создали почтовый ящик с помощью API CreateMailslot(), вы читаете из него с помощью API ReadFile(). Дескриптор почтового ящика создается в режиме совмещенного ввода-вывода, поэтому на нем можно использовать совмещенный ввод-вывод, хотя можно использовать не совмещенный ввод-вывод, если он лучше подходит для вашей модели. Вы также можете вызвать API GetMailslotInfo(), чтобы запросить, сколько сообщений ждут, какова длина следующего сообщения, и каково время ожидания. Вы можете вызвать API SetMailslotInfo(), чтобы изменить время ожидания. Знайте, что дескриптор, передаваемый этим двум API, должен быть создан API CreateMailslot().

Если вы не создали почтовый ящик, то подключайтесь к почтовому ящику с помощью API CreateFile(). В этом случае вы можете писать в него с помощью API WriteFile(). Можете ли вы использовать совмещенный ввод-вывод, зависит от способа вызова API CreateFile(); он бывает синхронным или асинхронным, в зависимости от ваших нужд. Нельзя подключиться к почтовому ящику с помощью CreateFile() и иметь возможность читать из него.

Глюк почтовых ящиков

Документация MSDN по почтовым ящикам говорит, что почтовый ящик существует, пока существует любой открытый дескриптор почтового ящика. Это неверно (в Windows XP Professional SP2). Может иметься любое количество открытых дескрипторов писателя для почтового ящика, но почтовый ящик исчезает, как только закрывается дескриптор читателя (и записи в почтовый ящик скрываются, как только дескриптор читателя закрывается). Это логично. Если можно иметь лишь одного читателя, нет смысла в существовании почтового ящика после того, как читатель исчез, так как любые сообщения, записанные в почтовый ящик, будут напрасно помещаться в буфер системой; если нет читателя, помещенные в буфер сообщения будут висеть там вечно (помните, что нельзя использовать CreateFile() для открытия дескриптора чтения для почтового ящика).

Другие рекомендации по почтовым ящикам

Если вы внимательно прочитаете документацию MSDN, то поймете, что мы несколько упростили правила при подключении писателя к существующему почтовому ящику. Наряду с указанием имени сервера, подключающегося к почтовому ящику на конкретном сервере, можно задать подключение ко всем почтовым ящикам с конкретным именем в домене. Это делается путем указания имени домена, а не имени сервера, следующим образом: \\domainname\mailslot\name. Также можно использовать звездочку * как условное обозначение для главного домена. На первый взгляд, это кажется прекрасным – можно создать любое количество читателей, работающих на разных компьютерах внутри домена, и писать во все из них одновременно путем задания имени домена. Но есть минус. Если вы подключаетесь к почтовому ящику в качестве писателя с помощью описателя домена, вы не можете записать в сообщении больше 424 байт. Зато, если ваше приложение может выдержать такое ограничение, это самый легкий способ контролировать один процесс из множества мест в домене.

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

Первый класс, CMailslot, является абстрактным базовым классом, инкапсулирующим основные составляющие почтовых ящиков.

Еще есть два производных класса: CSyncMailslotReader –  реализующий сервер (читатель) почтового ящика, и CSyncMailslotWriter –  реализующий клиент (писатель) почтового ящика. С помощью этих двух классов поток, вызывающий соответствующую функцию-член (читать или писать), блокируется до тех пор, пока операция не завершится.

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

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