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

Цель: Изучение механизмов взаимодействия между процессами обмена сообщениями в операционной системе 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);

}

Варианты заданий к лабораторной работе №7

Вариант №1

Разработать две программы – сервер и клиент. Клиент отсылает серверу через почтовый ящик два числа L и U, введенные пользователем, где L – это нижняя граница диапазона, U – верхняя граница диапазона. Сервер принимает значения границ диапазона из почтового ящика, вычисляет сумму и произведение чисел от L до U и выводит полученные значения на экран.

Вариант №2

Разработать две программы – сервер и клиент. Клиент отсылает серверу введенный пользователем номер числа Фибоначчи через именованный конвейер. Сервер принимает из именованного конвейера номер, вычисляет число Фибоначчи с этим номером, по формуле Fi = Fi –1 + Fi –2, F 0 = F 1 = 1 и выводит его на экран.

Вариант №3

Разработать две программы – сервер и клиент. Клиент отсылает серверу через почтовый ящик введенную пользователем строку, хранящую знаковое целое число. Сервер принимает из почтового ящика строку, хранящую знаковое целое число, и выводит на экран строковый эквивалент этого числа прописью (например, ввод «-1211» должен приводить к выводу «минус тысяча двести одиннадцать»).

Вариант №4

Разработать две программы – сервер и клиент. Клиент отсылает серверу через именованный конвейер введенную пользователем строку, хранящую число со знаком и плавающей точкой. Сервер принимает из именованного конвейера строку, хранящую число со знаком и плавающей точкой, и выводит на экран строковый эквивалент этого числа прописью (например, ввод «-12.11» должен приводить к выводу «минус двенадцать целых одиннадцать сотых»).

Вариант №5

Разработать две программы – сервер и клиент. Клиент отсылает серверу через почтовый ящик две строки, введенные пользователем. Сервер принимает из почтового ящика две строки. Далее, если обе строки хранят целые числа со знаком, то на экран выводится сумма чисел, в противном случае – конкатенация двух введенных строк.

Вариант №6

Разработать две программы – сервер и клиент. Клиент отсылает серверу через именованный конвейер элементы двух прямоугольных матриц, введенные пользователем. Сервер принимает из именованного конвейера две прямоугольных матрицы, а затем выводит на экран их сумму и произведение.

Вариант №7

Разработать две программы – сервер и клиент. Клиент отсылает серверу через почтовый ящик элементы вектора (одномерного целочисленного массива), введенные пользователем. Сервер принимает вектор из почтового ящика, упорядочивает его по возрастанию любым из так называемых «улучшенных алгоритмов» сортировки массивов и выводит на экран.

Вариант №8

Разработать две программы – сервер и клиент. Клиент отсылает серверу через именованный конвейер элементы вектора (одномерного массива чисел с плавающей точкой), введенные пользователем. Сервер принимает вектор из именованного конвейера, упорядочивает его по возрастанию любым из так называемых «улучшенных алгоритмов» сортировки массивов и выводит на экран.

Вариант №9

Разработать две программы – сервер и клиент. Клиент отсылает серверу через почтовый ящик элементы вектора (одномерного массива строк), введенные пользователем. Сервер принимает вектор из почтового ящика, упорядочивает его по возрастанию любым из так называемых «улучшенных алгоритмов» сортировки массивов и выводит на экран.

Вариант №10

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

Вариант №11

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

Вариант №12

Разработать две программы – сервер и клиент. Клиент принимает от пользователя две даты – строки вида ЦЦ.ЦЦ.ЦЦЦЦ, где Ц – это любая цифра из диапазона [0-9] и отсылает серверу через именованный конвейер. Сервер принимает даты из именованного конвейера, вычисляет полное количество дней, прошедших между двумя полученными датами, и выводит его на
экран.

Вариант №13

Разработать две программы – сервер и клиент. Клиент принимает от пользователя два значения времени – строки вида ЦЦ.ЦЦ.ЦЦ, где Ц – это любая цифра из диапазона [0-9], и отсылает серверу через почтовый ящик. Сервер принимает из почтового ящика обе строки, вычисляет полное количество секунд, прошедших между двумя значениями времени, и выводит его на экран.

Вариант №14

Разработать две программы – сервер и клиент. Клиент отсылает серверу через именованный конвейер две строки, введенные пользователем. Сервер принимает из именованного конвейера две строки, осуществляет поиск вхождения второй строки в первую любым известным методом, кроме прямого (алгоритм Кнута-Мориса-Пратта, алгоритм Боуэра-Мура), и выводит на экран значение индекса элемента первой строки, с которого началось совпадение, или -1 в противном случае.

Вариант №15

Разработать две программы – сервер и клиент. Клиент отсылает серверу через почтовый ящик две строки, введенные пользователем. Сервер принимает из почтового ящика две строки, осуществляет поиск количества вхождений второй строки в первую любым известным методом, кроме прямого (алгоритм Кнута-Мориса-Пратта, алгоритм Боуэра-Мура), и выводит на
экран полученное значение.

Вариант №16

Разработать две программы – сервер и клиент. Клиент принимает от пользователя дату – строку вида ЦЦ.ЦЦ.ЦЦЦЦ, где Ц – это любая цифра из диапазона [0-9,] и отсылает серверу через именованный конвейер. Сервер принимает из именованного конвейера дату и выводит на экран число и месяц прописью, а за последними четырьмя – слово «года» (например, ввод «29.02.2008» приводит к выводу «Двадцать девятое февраля 2008 года»).

Вариант №17

Разработать две программы – сервер и клиент. Клиент принимает от пользователя значение времени – строку вида ЦЦ.ЦЦ.ЦЦ, где Ц – это любая цифра из диапазона [0-9] и отсылает серверу через почтовый ящик. Сервер принимает значение времени из почтового ящика и выводит на экран значение часов минут и секунд прописью (например, ввод «12.01.20» приводит к выводу «двенадцать часов одна минута двадцать секунд»).

Вариант №18

Разработать две программы – сервер и клиент. Клиент принимает от пользователя строку из нулей и единиц – «битовую строку» и отсылает серверу через именованный конвейер. Сервер принимает из именованного конвейера битовую строку, инвертирует ее, выводит на экран значение инвертированной строки, переводит ее в число в десятичный формат и выводит полученное число на экран.

Вариант №19

Разработать две программы – сервер и клиент. Клиент принимает от пользователя строку из нулей и единиц – «битовую строку» и отсылает серверу через почтовый ящик. Сервер принимает битовую строку, осуществляет ее реверс, когда нули заменяются на единицы, а единицы на нули. Полученная строка выводится на экран, затем программа переводит ее в число в десятичном формате и выводит полученное число на экран.

Вариант №20

Разработать две программы – сервер и клиент. Клиент отсылает число, введенное пользователем, серверу через именованный конвейер. Сервер принимает число из именованного конвейера, вычисляет его факториал по формуле N! = N*(N-1)!, где 0!=1, и выводит его на экран.

Вариант №21

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

Вариант №22

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

Вариант №23

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

Вариант №24

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

Вариант №25

Разработать две программы – сервер и клиент. Клиент принимает от пользователя беззнаковое целое число и отсылает серверу через именованный конвейер. Сервер принимает число из именованного конвейера. Если оно является степенью двойки, то на экран выводится показатель степени, и сообщение «не является степенью двойки» в противном случае.


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



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