Лабораторная работа № 8. Организация взаимодействия между процессами посредством почтовых ящиков и конвейеров

 

Цель: Изучение механизмов взаимодействия между процессами обмена сообщениями в операционной системе Windows.

 

Задачи:

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

2. Составление алгоритма программы.

3. Программная реализация.

 

Ход работы:

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

2. Разработать и отладить сервер и клиент в соответствии с полученным заданием.

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

Ход защиты:

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

2. Пояснить программный код разработанных приложений.

 

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

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

\\сервер\Mailslot\[path]name

где сервер – имя сервера, на котором создается ПЯ и выполняется серверное приложение, Mailslot – фиксированная обязательная строка, [ path ] name – имя почтового ящика. В качестве сервера может быть задана точка (.), снежинка (*), имя домена или сервера.

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

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

1. Создать описатель ПЯ с помощью CreateMailSlot.

2. Получить данные от любого клиента путем вызова ReadFile с описателем ПЯ в качестве параметра.

3. Закрыть описатель ПЯ с помощью CloseHandle.

 

Первая из упомянутых функций определена так:

HANDLE CreateMailSlot(

LPCTSTR Name,  // имя ПЯ

DWORD MaxMessSize, 

// максимальный размер сообщения (0 – любой размер)

DWORD TimeOut, // Время ожидания сообщений, в мс.

LPSECURITY_ATTRIBUTES Attrib); //атрибуты безопасности

 

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

HANDLE ReadFile(

HANDLE File,  // описатель файла

LPVOID Buffer, // буфер для сообщения

DWORD BytesToRead,

// размер буфера (обязательно больше размера сообщения)

LPDWORD BytesRead, // количество прочитанных байтов

LPOVERLAPPED Overlap); // асинхронное чтение данных из буфера

 

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

#include <windows.h>

#include <stdio.h>

 

void main(void)

{

HANDLE Mailslot;

char buffer[256];

DWORD NumberOfBytesRead;

// Создание почтового ящика и бесконечное ожидание сообщений

if ((Mailslot = CreateMailslot("\\\\.\\Mailslot\\slot1", 0,

    MAILSLOT_WAIT_FOREVER, NULL)) == INVALID_HANDLE_VALUE)

{

    printf("Ошибка при создании ПЯ %d\n", GetLastError());

    return;

}

 

// Бесконечное чтение данных из ПЯ!

while(ReadFile(Mailslot, buffer, 256, &NumberOfBytesRead,

    NULL)!= 0)

{

    buffer[NumberOfBytesRead] = 0;

    printf("%s\n", buffer);

}

CloseHandle (Mailslot);

}

 

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

1. Открыть описатель ПЯ, в который нужно записать данные с помощью CreateFile.

2. Записать данные в ПЯ вызовом функции WriteFile.

3. Закрыть описатель ПЯ с помощью CloseHandle.

 

Открытие файла осуществляется при помощи функции

HANDLE CreateFile (

LPCTSTR Name,         // имя ПЯ

DWORD DesAccess, // желаемый доступ

DWORD Sharing,    // режим разделения

LPSECURITY_ATTRIBUTES Attrib, //атрибуты безопасности

DWORD CreatDispos, // создать или открыть

DWORD Flags,  // флаги и атрибуты

HANDLE Template); // шаблон

 

Поскольку клиент может только записывать данные на сервер, то параметр DesAccess может быть только GENERIC_WRITE. Параметр Sharing должен быть только FILE_SHARE_READ, чтобы сервер мог открывать и читать из почтового ящика. Флаг CreateDispos обязан быть OPEN_EXISTING, а FlagsFILE_ATTRIBUTE_NORMAL.

 

Теперь можно записывать данные в ПЯ, для этого вызвать функцию

HANDLE WriteFile(

HANDLE File,  // описатель файла

LPVOID Buffer, // буфер для сообщения

DWORD BytesToWrite,

// размер буфера (обязательно больше размера сообщения)

LPDWORD BytesWritten,// количество записанных байтов

LPOVERLAPPED Overlap); // асинхронное чтение данных из буфера

 

Пример клиента почтового ящика приведен ниже.

#include <windows.h>

#include <stdio.h>

 

void main(int argc, char *argv[])

{

HANDLE Mailslot;

DWORD BytesWritten;

CHAR ServerName[256];

 

// Вид командной строки для сервера,

// которому отправляется сообщение

if (argc < 2)

{

    printf("Использование: client <server_name>\n");

    return;

}

sprintf(ServerName, "\\\\%s\\Mailslot\\Slot1", argv[1]);

 

if ((Mailslot = CreateFile(ServerName, GENERIC_WRITE,

    FILE_SHARE_READ, NULL, OPEN_EXISTING,

     FILE_ATTRIBUTE_NORMAL, NULL)) ==

    INVALID_HANDLE_VALUE)

{

    printf("Создание файла с ошибкой %d\n",

             GetLastError());

    return;

}

 

if (WriteFile(Mailslot, "Это тестовое сообщение",

      sizeof ("Это тестовое сообщение"),

      &BytesWritten, NULL) == 0)

{

    printf("Запись в файл с ошибкой %d\n", GetLastError());

    return;

}

printf("Записано %d байтов\n", BytesWritten);

CloseHandle(Mailslot);

}

 

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

ИК основаны на интерфейсе файловой системы. Клиенты и серверы используют стандартные функции Win 32 API для отправки и получения данных именованным конвейером, а также правила именования файловой системы, а именно:

\\сервер\Pipe\[path]name

где сервер – имя сервера, на котором создается ИК, Pipe – фиксированная обязательная строка, [ path ] name – имя ИК. В качестве сервера может быть задана точка (.) и имя сервера. ИК нельзя создать на удаленном компьютере.

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

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

Процесс создания сервера заключается в последовательном вызове AP I-функций:

1. Создать экземпляр ИК с помощью CreateNamedPipe.

2. «Слушать» клиентские соединения с помощью ConnectNamedPipe.

3. Читать и записывать данные в ИК с помощью ReadFile и WriteFile.

4. Завершить соединение с помощью DisconnectNamedPipe.

5. Закрыть описатель экземпляра ИК с помощью CloseHandle.

 

Первая из упомянутых функций определена так.

HANDLE CreateNamedPipe(

LPCTSTR Name, // имя ПЯ

DWORD OpenMode,  // направление передачи,

                // управление вводом-выводом и безопасность

DWORD PipeMode, // режим операций чтения, записи, ожидания

DWORD MaxInst, // максимальное количество экземпляров канала

DWORD OutSize, // размер выходящего буфера

DWORD InSize, // размер входящего буфера

DWORD TimeOut, // Время ожидания соединения, в мс.

LPSECURITY_ATTRIBUTES Attrib); //атрибуты безопасности

 

Флаги режимов могут быть следующими: PIPE_ACCESS_DUPLEX
(передача данных в обоих направлениях), PIPE_ACCESS_OUTBOUND (данные передаются от сервера к клиенту) и PIPE_ACCESS_INBOUND (от клиента к серверу), FILE_FLAG_WRITE_THROUGH (функции записи не возвращают значение, пока данные передаются по сети или находятся в буфере), FILE_FLAG_OVERLAPPED (используется перекрытый ввод-вывод), WRITE_DAC (можно изменять список избирательного управления доступом), ACCESS_SYSTEM_SECURITY (можно изменять системный список управления доступом), WRITE_OWNER (можно изменять владельца ИК и групповой идентификатор безопасности).

Параметр PipeMode определяет режимы чтения, записи и ожидания. При создании нужно указать по одному флагу из каждой категории, объединив их операцией «|». Для записи данных флаг PIPE_TYPE_BYTE означает, что данные записываются в ИК потоком байтов, PIPE_TYPE_MESSAGE – потоком сообщений. Для чтения PIPE_READMODE_BYTE означает чтение данных потоком байтов, а PIPE_READMODE_MESSAGE – потоком сообщений. Для операции ожидания PIPE_WAIT включает режим блокировки, PIPE_NOWAIT – отключает режим блокировки.

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

BOOL ConnectNamedPipe(

HANDLE Pipe,  // описатель ИК

LPOVERLAPPED Overlap); // асинхронное чтение данных из буфера

 

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

#include <windows.h>

#include <stdio.h>

 

void main(void)

{

HANDLE NPipe;

char buffer[256];

DWORD NumberOfBytesRead;

 

// Создание ИК

if ((NPipe = CreateNamedPipe("\\\\.\\Pipe\\pipe1",

      PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE |

      PIPE_READMODE_BYTE, 1, 0, 0, 2000, NULL)) ==

     INVALID_HANDLE_VALUE)

{

    printf("Ошибка при создании конвейера %d\n",

             GetLastError());

    return; }

printf (“Сервер запущен.\n”);

 

if (ConnectNamedPipe(NPipe, NULL) == 0)

{

    printf("Ошибка при установлении соединения %d\n",

            GetLastError());

       CloseHandle (NPipe);

       return;

}

if (ReadFile (NPipe, buffer, sizeof (buffer),

   &NumberOfBytesRead, NULL) <= 0)

{

    printf("Ошибка при чтении из конвейера %d\n",

            GetLastError());

       CloseHandle (NPipe);

       return;

           

}

printf (“%s\n”, buffer);

 

if (DisconnectNamedPipe(NPipe) == 0)

{

    printf("Ошибка при разрыве соединения %d\n",

             GetLastError());

       return;

}

CloseHandle (NPipe);

}

 

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

1. Для проверки наличия свободного экземпляра ИК нужно вызвать функцию WaitNamedPipe.

2. Для установления соединения нужно использовать CreateFile.

3. Читать и записывать данные в ИК с помощью ReadFile и WriteFile.

4. Завершить соединение с помощью функции CloseHandle.

 

Перед установлением соединения клиент должен проверить наличие свободного ИК с помощью функции

BOOL WaitNamedPipe (

LPCTSTR Name, // имя ИК

DWORD TimeOut); // величина ожидания свободного ИК

 

Затем клиент может открыть этот экземпляр ИК с помощью CreateFile, указав в качестве имени название открываемого ИК. Поскольку клиент
может при желании читать и/или записывать данные на сервер, то параметр DesAccess может быть GENERIC_WRITE либо GENERIC_READ либо GENERIC_WRITE | GENERIC_READ. Параметр Sharing должен быть только 0, поскольку только один клиент получает доступ к ИК. Флаг CreateDispos обязан быть OPEN_EXISTING, а Flags должен обязательно включать FILE_ATTRIBUTE_NORMAL.

 

Пример клиента именованного конвейера приведен ниже.

#include <windows.h>

#include <stdio.h>

 

void main(void)

{

HANDLE NPipe;

DWORD BytesWritten;

 

if (WaitNamedPipe("\\\\.\\Pipe\\pipe1",

    NMPWAIT_WAIT_FOREVER) == 0)

{

    printf("Ожидание соединения с ошибкой %d\n",

            GetLastError());

    return;

}

 

// создание описателя ИК

if ((NPipe = CreateFile("\\\\.\\Pipe\\pipe1", GENERIC_READ |

    GENERIC_WRITE, 0, (LPSECURITY_ATTRIBUTES)NULL,

    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,

    (HANDLE)NULL)) == INVALID_HANDLE_VALUE)

{

    printf("Создание файла с ошибкой %d\n",

            GetLastError());

    return;

}

 

if (WriteFile (NPipe, “Test string”,

     strlen (“Test string”),

     &BytesWritten, NULL) == 0)

{

    printf("Запись в файл с ошибкой %d\n", GetLastError());

       CloseHandle (NPipe);

    return;

          

}

printf("Записано %d байтов\n", BytesWritten);

 

CloseHandle(NPipe);

}







Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: