Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 27, часть 2, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Каналы передачи данных Mailslot

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

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

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

С помощью каналов Pipe вы не сможете передавать данные в широковещательном режиме, так как только два процесса могут создать канал типа Pipe.

Создание канала Mailslot

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

Приведем прототип функции CreateMailslot:


HANDLE CreateMailslot(
  LPCTSTR lpName,          // адрес имени канала Mailslot
  DWORD   nMaxMsgSize,     // максимальный размер сообщения
  DWORD   lReadTimeout,    // время ожидания для чтения
  LPSECURITY_ATTRIBUTES lpSecurityAttributes); // адрес 
                           // структуры защиты

Через параметр lpName вы должны передать функции CreateMailslot адрес строки символов с именем канала Mailslot. Эта строка имеет следующий вид:


\\.\mailslot\[Путь]ИмяКанала

В этом имени путь является необязательной компонентой. Тем не менее, вы можете указать его аналогично тому, как это делается для файлов. Что же касается имени канала Mailslot, то оно задается аналогично имени канала Pipes.

Параметр nMaxMsgSize определяет максимальный размер сообщений, передаваемых через создаваемый канал Mailslot. Вы можете указать здесь нулевое значение, при этом размер сообщений не будет ограничен. Есть, однако, одно исключение - размер широковещательных сообщений, передаваемых всем рабочим станциям и серверам домена не должен превышать 400 байт.

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

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

При ошибке функцией CreateMailslot возвращается значение INVALID_HANDLE_VALUE. Код ошибки можно определить при помощи функции GetLastError.

Ниже мы привели пример использования функции CreateMailslot в серверном приложении:


LPSTR  lpszMailslotName = "\\\\.\\mailslot\\$MailslotName$";
hMailslot = CreateMailslot(lpszMailslotName, 0,
  MAILSLOT_WAIT_FOREVER, NULL);

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

Время ожидания указано как MAILSLOT_WAIT_FOREVER, поэтому функции, работающие с данным каналом Mailslot, будут работать в блокирующем режиме.

Открытие канала Mailslot

Прежде чем приступить к работе с каналом Mailslot, клиентский процесс должен его открыть. Для выполнения этой операции следует использовать функцию CreateFile, например, так:


LPSTR  lpszMailslotName = "\\\\.\\mailslot\\$MailslotName$";
hMailslot = CreateFile(
  lpszMailslotName, GENERIC_WRITE,
  FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

Здесь в качестве первого параметра функции CreateFile передается имя канала Mailslot. Заметим, что вы можете открыть канал Mailslot, созданный на другой рабочей станции в сети. Для этого строка имени канала, передаваемая функции CreateFile, должна иметь следующий вид:


\\ИмяРабочейСтанции\mailslot\[Путь]ИмяКанала

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


\\ИмяДомена\mailslot\[Путь]ИмяКанала

Для передачи сообщений одновременно всем рабочим станциям сети первичного домена имя задается следующим образом:


\\*\mailslot\[Путь]ИмяКанала

В качестве второго параметра функции CreateFile мы передаем константу GENERIC_WRITE. Эта константа определяет, что над открываемым каналом будет выполняться операция записи. Напомним, что клиентский процесс может только посылать сообщения в канал Mailslot, но не читать их оттуда. Чтение сообщений из канала Mailslot - задача для серверного процесса.

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

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

Запись сообщений в канал Mailslot

Запись сообщений в канал Mailslot выполняет клиентский процесс, вызывая для этого функцию WriteFile. С этой функцией вы уже имели дело:


HANDLE hMailslot;
char   szBuf[512];
DWORD  cbWritten;
WriteFile(hMailslot, szBuf, strlen(szBuf) + 1,
  &cbWritten, NULL);

В качестве первого параметра этой функции необходимо передать идентификатор канала Mailslot, полученный от функции CreateFile.

Второй параметр определяет адрес буфера с сообщением, третий - размер сообщения. В нашем случае сообщения передаются в виде текстовой строки, закрытой двоичным нулем, поэтому для определения длины сообщения мы воспользовались функцией strlen.

Чтение сообщений из канала Mailslot

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


HANDLE hMailslot;
char   szBuf[512];
DWORD  cbRead;
ReadFile(hMailslot, szBuf, 512, &cbRead, NULL);

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

Заметим, что перед выполнением операции чтения следует проверить состояние канала Mailslot. Если в нем нет сообщений, то функцию ReadFile вызывать не следует. Для проверки состояния канала вы должны воспользоваться функцией GetMailslotInfo, описанной ниже.

Определение состояния канала Mailslot

Серверный процесс может определить текущее состояние канала Mailslot по его идентификатору с помощью функции GetMailslotInfo. Прототип этой функции мы привели ниже:


BOOL GetMailslotInfo(
  HANDLE  hMailslot,        // идентификатор канала Mailslot
  LPDWORD lpMaxMessageSize, // адрес максимального размера 
                            // сообщения
  LPDWORD lpNextSize,    // адрес размера следующего сообщения
  LPDWORD lpMessageCount,   // адрес количества сообщений
  LPDWORD lpReadTimeout);   // адрес времени ожидания

Через параметр hMailslot функции передается идентификатор канала Mailslot, состояние которого необходимо определить.

Остальные параметры задаются как указатели на переменные типа DWORD, в которые будут записаны параметры состояния канала Mailslot.

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

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

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

И, наконец, в переменную, адрес которой задается в параметре lpReadTimeout, записывается текущее время ожидания, установленное для канала (в миллисекундах).

Если вам не нужна вся информация, которую можно получить с помощью функции GetMailslotInfo, некоторые из ее параметров (кроме, разумеется, первого) можно указать как NULL.

В случае успешного завершения функция GetMailslotInfo возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.

Ниже мы привели пример использоания функции GetMailslotInfo:


BOOL   fReturnCode;
DWORD  cbMessages;
DWORD  cbMsgNumber;
fReturnCode = GetMailslotInfo(hMailslot, NULL, &cbMessages,
  &cbMsgNumber, NULL);

Изменение состояния канала Mailslot

С помощью функции SetMailslotInfo серверный процесс может изменить время ожидания для канала Mailslot уеж после его создания.

Прототип функции SetMailslotInfo приведен ниже:


BOOL SetMailslotInfo(
  HANDLE hMailslot,      // идентификатор канала Mailslot
  DWORD  dwReadTimeout); // время ожидания

Через параметр hMailslot функции SetMailslotInfo передается идентификатор канала Mailslot, для которого нужно изменить время ожидания.

Новое значение времени ожидания в миллисекундах задается через параметр dwReadTimeout. Вы также можете указать здесь константы 0 или MAILSLOT_WAIT_FOREVER. В первом случае функции, работающие с каналом, вернут управление немедленно, во втором - будут находиться в состоянии ожидания до тех пор, пока не завершится выполняемая операция.

Примеры приложений

Для примера мы подготовили исходные тексты двух приложений - MSLOTS и MSLOTC, которые обмениваются информацией через канал Mailslot (рис. 2.6).

Рис. 2.6. Обмен сообщениями между приложениями MSLOTS и MSLOTC

Приложение MSLOTS выполняет роль сервера, создавая канал Mailslot. Периодически с интервалом 0,5 с это приложение проверяет, не появилось ли в канале сообщение. Если появилось, это сообщение отображается в консольном окне.

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

Если вводить текстовые строки в приглашении cmd>, которое выводится в консольном окне клиентского приложения MSLOTC, они будут передаваться серверу через канал Mailslot и отображаться в его окне. Для завершения работы обоих приложений вы должны ввести в приглашении клиента команду exit.

Перейдем теперь к описанию исходных текстов наших приложений.

Приложение MSLOTS

Исходный текст серверного приложения MSLOTS представлен в листинге 2.13.

Листинг 2.13. Файл mailslot/mslots/mslots.c


// ==================================================
// Приложение MSLOTS (серверное приложение)
// Демонстрация использования каналов Mailslot
// для передачи данных между процессами
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================

#include <windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
  // Код возврата из функций
  BOOL   fReturnCode; 

  // Размер сообщения в байтах
  DWORD  cbMessages;

  // Количество сообщений в канале Mailslot
  DWORD  cbMsgNumber;

  // Идентификатор канала Mailslot
  HANDLE hMailslot;

  // Имя создаваемого канала Mailslot
  LPSTR  lpszMailslotName = "\\\\.\\mailslot\\$Channel$";

  // Буфер для передачи данных через канал
  char   szBuf[512];

  // Количество байт данных, принятых через канал
  DWORD  cbRead;

  printf("Mailslot server demo\n"
    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");

  // Создаем канал Mailslot, имеющий имя lpszMailslotName
  hMailslot = CreateMailslot(
    lpszMailslotName, 0,
    MAILSLOT_WAIT_FOREVER, NULL);
    
  // Если возникла ошибка, выводим ее код и зваершаем
  // работу приложения
  if(hMailslot == INVALID_HANDLE_VALUE)
  {
    fprintf(stdout,"CreateMailslot: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Выводим сообщение о создании канала
  fprintf(stdout,"Mailslot created\n"); 

  // Цикл получения команд через канал
  while(1)
  {
    // Определяем состояние канала Mailslot
    fReturnCode = GetMailslotInfo(
      hMailslot, NULL, &cbMessages,
      &cbMsgNumber, NULL);

    if(!fReturnCode)
    {
      fprintf(stdout,"GetMailslotInfo: Error %ld\n", 
        GetLastError());
      getch();
      break;
    }

    // Если в канале есть Mailslot сообщения,
    // читаем первое из них и выводим на экран
    if(cbMsgNumber != 0)
    {
      if(ReadFile(hMailslot, szBuf, 512, &cbRead, NULL))
      {
        // Выводим принятую строку на консоль 
        printf("Received: <%s>\n", szBuf);
      
        // Если пришла команда "exit", 
        // завершаем работу приложения
        if(!strcmp(szBuf, "exit"))
          break;
      }
      else
      {
        fprintf(stdout,"ReadFile: Error %ld\n", 
          GetLastError());
        getch();
        break;
      }
    }
    
    // Выполняем задержку на  500 миллисекунд
    Sleep(500);
  }

  // Перед завершением приложения закрываем
  // идентификатор канала Mailslot
  CloseHandle(hMailslot);
  return 0;
}

Прежде всего, серверное приложение создает канал Mailslot, пользуясь для этого функцией CreateMailslot:


hMailslot = CreateMailslot(lpszMailslotName, 0,
  MAILSLOT_WAIT_FOREVER, NULL);

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

Сообщение читается функцией ReadFile:


ReadFile(hMailslot, szBuf, 512, &cbRead, NULL);

После чтения перед выполнением очередной проверки состояния приложение выполняет задержку, вызывая для этого функцию Sleep:


Sleep(500);

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

Перед завершением работы приложения мы закрываем идентификатор канала Mailslotс помощью функции CloseHandle:


CloseHandle(hMailslot);

Приложение MSLOTС

Исходный текст клиентского приложения MSLOTC представлен в листинге 2.14.

Листинг 2.14. Файл mailslot/mslotc/mslotc.c


// ==================================================
// Приложение MSLOTC (клиентское приложение)
// Демонстрация использования каналов Mailslot
// для передачи данных между процессами
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================

#include <windows.h>
#include <stdio.h>
#include <conio.h>

DWORD main(int argc, char *argv[])
{
  // Идентификатор канала Mailslot
  HANDLE hMailslot;

  // Буфер для имени канала Mailslot
  char   szMailslotName[256];

  // Буфер для передачи данных через канал
  char   szBuf[512];

  // Количество байт, переданных через канал
  DWORD  cbWritten;

  printf("Mailslot client demo\n"
    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");

  printf("Syntax: mslotc [servername]\n");

  // Если при запуске было указано имя срвера,
  // указываем его в имени канала Mailslot
  if(argc > 1)
    sprintf(szMailslotName, "\\\\%s\\mailslot\\$Channel$",
      argv[1]);
  
  // Если имя сервера задано не было, создаем канал
  // с локальным процессом
  else
    strcpy(szMailslotName, "\\\\.\\mailslot\\$Channel$");

  // Создаем канал с процессом MSLOTS
  hMailslot = CreateFile(
    szMailslotName, GENERIC_WRITE,
    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    
  // Если возникла ошибка, выводим ее код и 
  // завершаем работу приложения
  if(hMailslot == INVALID_HANDLE_VALUE)
  {
    fprintf(stdout,"CreateFile: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Выводим сообщение о создании канала
  fprintf(stdout,"\nConnected. Type 'exit' to terminate\n"); 

  // Цикл посылки команд через канал
  while(1)
  {
    // Выводим приглашение для ввода команды
    printf("cmd>");
    
    // Вводим текстовую строку
    gets(szBuf);

    // Передаем введенную строку серверному процессу
    // в качестве команды
    if(!WriteFile(hMailslot, szBuf, strlen(szBuf) + 1,
      &cbWritten, NULL))
      break;
    
    // В ответ на команду "exit" завершаем цикл
    // обмена данными с серверным процессом
    if(!strcmp(szBuf, "exit"))
      break;
  }

  // Закрываем идентификатор канала
  CloseHandle(hMailslot);
  return 0;
}

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

Канал открывается следующим образом:


hMailslot = CreateFile( szMailslotName, GENERIC_WRITE,
  FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

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

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


WriteFile(hMailslot, szBuf, strlen(szBuf) + 1,
    &cbWritten, NULL);

Перед завершением своей работы приложение MSLOTC закрывает канал, вызывая для этого функцию CloseHandle XE.

[Назад] [Содеожание] [Дальше]