Главная
страница 1


Лабораторная работа № 6

Межпроцессное взаимодействие

Цель работы: Изучение механизмов межпроцессного взаимодействия в Windows NT. Получение практических навыков по использованию Win32 API для программирования механизмов IPC

Отображение файлов


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

Программист может использовать специальный вид отображения файла для обеспечения именованной разделенной памяти между процессами. Если при создании объекта-отображения файла указывается файл подкачки системы (swapping file), то отображение файла создается как блок совместно используемой памяти. Другие процессы также могут получить доступ к этому блоку, открыв такое же отображение файла.

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

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



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



1. Создание именованной совместно используемой памяти.

Первый процесс вызывает функцию CreateFileMapping для того, чтобы создать объект-отображение файла с именем MyFileMappingObject. Используя флаг PAGE_READWRITE, процесс назначает права на чтение и запись данных при работе с файловым отображением.


Функция CreateFileMapping создает или открывает именованный или неименованный объект-отображение файла и имеет следующее определение (по MSDN 2001):
HANDLE CreateFileMapping(

HANDLE hFile, // дескриптор файла для отображения

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

DWORD flProtect, // флаги защиты

DWORD dwMaximumSizeHigh, // старшие DWORD максимального размера

DWORD dwMaximumSizeLow, // младшие DWORD максимального размера

LPCTSTR lpName // имя объекта-отображения файла

);
HANDLE hMapFile;


hMapFile = CreateFileMapping(hFile, // Дескриптор открытого файла

NULL, // Безопасность по умолчанию

PAGE_READWRITE, // Разрешение чтения/записи

0, // Максимальный размер объекта

0, // Текущий размер hFile

"MyFileMappingObject"); // Имя отображения файла


// Проверяем корректность создания отображения

if (hMapFile == NULL)

ErrorHandler("Не могу создать объект file-mapping.");
Затем процесс использует дескриптор hMapFile при вызове функции MapViewOfFile, чтобы создать представление содержимого файла в адресном пространстве процесса. Функция возвращает указатель на представление файла в памяти.
Синтаксис функции MapViewOfFile по MDSN 2001:
LPVOID MapViewOfFile(

HANDLE hFileMappingObject, // дескриптор объекта-отображения файла

DWORD dwDesiredAccess, // режим доступа

DWORD dwFileOffsetHigh, // старший DWORD смещения

DWORD dwFileOffsetLow, // младший DWORD смещения

SIZE_T dwNumberOfBytesToMap // количество байт для сопоставления

);
LPVOID lpMapAddress;


lpMapAddress = MapViewOfFile(hMapFile, // Дескриптор объекта-отображения файла

FILE_MAP_ALL_ACCESS, // Разрешение чтения/записи

0, // Максимальный размер объекта

0, // Размер hFile

0); // Отображать файл целиком

if (lpMapAddress == NULL)

ErrorHandler("Не могу создать представление файла в памяти процесса.");
Второй процесс вызывает функцию OpenFileMapping с именем MyFileMappingObject, чтобы использовать тот же объект-отображение файла, что и первый процесс. Также как и первый процесс, второй использует функцию MapViewOfFile для сопоставления области памяти процесса отображаемому файлу.
Синтаксис функции OpenFileMapping по MDSN 2001:
HANDLE OpenFileMapping(

DWORD dwDesiredAccess, // режим доступа

BOOL bInheritHandle, // флаг наследования

LPCTSTR lpName // имя объекта

);
HANDLE hMapFile;

LPVOID lpMapAddress;
hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, // Разрешение чтения/записи

FALSE, // Не наследовать имя

"MyFileMappingObject"); // объекта-отображения файла.

if (hMapFile == NULL)

ErrorHandler("Не могу открыть объект-отображение файла.");
lpMapAddress = MapViewOfFile(hMapFile, // Дескриптор объекта-отображения файла.

FILE_MAP_ALL_ACCESS, // Разрешение чтения/записи

0, // Максимальный размер объекта

0, // Размер hFile.

0); // Отображать файл целиком

if (lpMapAddress == NULL)

ErrorHandler("Не могу создать представление файла в памяти процесса.");
2. Чтение/запись отображенных данных.

Для чтения из представления файла в памяти разыменуем указатель, полученный с помощью функции MapViewOfFile:


DWORD dwLength;
dwLength = *((LPDWORD) lpMapAddress);
Тот же указатель используется и для записи данных в отображенный файл:
*((LPDWORD) lpMapAddress) = dwLength;
Функция FlushViewOfFile копирует указанное количество байт представления файла в памяти в физический файл не ожидая пока произойдет операция кэшированной записи:
if (!FlushViewOfFile(lpMapAddress, dwBytesToFlush))

ErrorHandler("Не могу сохранить изменения на диск.");


3. Завершение работы с отображениями.

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


if (!UnmapViewOfFile(lpMapAddress))

ErrorHandler("Не могу уничтожить сопоставление.");


Когда все процессы завершат использование объекта-отображения файла (вызвав предыдущую функцию), они должны закрыть дескрипторы объектов-отображений с помощью функции CloseHandle:
CloseHandle(hMapFile);
Замечание. В данном примере оставлены на самостоятельное рассмотрение вопросы, связанные с синхронизацией работы нескольких процессов, передающих данные через отображения файлов. Для обеспечения синхронизации см. лабораторную работу ОС-5.

Почтовые ящики (mailslot)


Почтовые ящики обеспечивают только однонаправленные соединения. Каждый процесс, который создает почтовый ящик, является «сервером почтовых ящиков» (mailslot server). Другие процессы, называемые «клиентами почтовых ящиков» (mailslot clients), посылают сообщения серверу, записывая их в почтовый ящик. Входящие сообщения всегда дописываются в почтовый ящик и сохраняются до тех пор, пока сервер их не прочтет. Каждый процесс может одновременно быть и сервером и клиентом почтовых ящиков, создавая, таким образом, двунаправленные коммуникации между процессами.

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

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

Mailslot является псевдофайлом находящимся в памяти и вы должны использовать стандартные функции для работы с файлами, чтобы получить доступ к нему. Данные в почтовом ящике могут быть в любой форме – их интерпретацией занимается прикладная программа, но их общий объем не должен превышать 64 Кб. Однако, в отличии от дисковых файлов, mailslot’ы являются временными — когда все дескрипторы почтового ящика закрыты, он и все его данные удаляются. Заметим, что все почтовые ящики являются локальными по отношению к создавшему их процессу; процесс не может создать удаленный mailslot.

Сообщения меньше, чем 425 байт, передаются с использованием дейтаграмм. Сообщения больше чем 426 байт используют передачу с установлением логического соединения на основе SMB-сеансов. Передачи с установлением соединения допускают только индивидуальную передачу от одного клиента к одному серверу. Следовательно, вы теряете возможность широковещательной трансляции сообщений от одного клиента ко многим серверам. Учтите, что Windows не поддерживает сообщения размером в 425 или 426 байт.

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



\\.\mailslot\[path]name

Например:



\\.\mailslot\taxes\bobs_comments

\\.\mailslot\taxes\petes_comments

\\.\mailslot\taxes\sues_comments
Если вы хотите отправить сообщение в почтовый ящик на удаленный компьютер, то воспользуйтесь NETBIOS-именем:

\\ComputerName\mailslot\[path]name
Если же ваша цель передать сообщение всем mailslot’ам с указанным именем внутри домена, вам понадобится NETBIOS-имя домена:

\\DomainName\mailslot\[path]name
Или для главного домена операционной системы (домен в котором находится рабочая станция):

\\*\mailslot\[path]name
Клиенты и серверы, использующие почтовые ящики, при работе с ними должны пользоваться следующими функциями:

Функции серверов почтовых ящиков


Функция

Описание

CreateMailslot

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

GetMailslotInfo

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

SetMailslotInfo

Изменение таймаута при чтении из почтового ящика.




Функция

Описание

DuplicateHandle

Дублирование дескриптора почтового ящика.

ReadFile, ReadFileEx

Считывание сообщений из почтового ящика.

GetFileTime

Получение даты и времени создания mailslot’а.

SetFileTime

Установка даты и времени создания mailslot’а.

GetHandleInformation

Получение свойств дескриптора почтового ящика.

SetHandleInformation

Установка свойств дескриптора почтового ящика.

Функции клиентов почтовых ящиков


Функция

Описание

CloseHandle

Закрывает дескриптор почтового ящика для клиентского процесса.

CreateFile

Создает дескриптор почтового ящика для клиентского процесса.

DuplicateHandle

Дублирование дескриптора почтового ящика.

WriteFile, WriteFileEx

Запись сообщений в почтовый ящик.

Рассмотрим последовательно все операции, необходимые для корректной работы с почтовыми ящиками.


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

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


HANDLE CreateMailslot(

LPCTSTR lpName, // имя почтового ящика

DWORD nMaxMessageSize, // максимальный размер сообщения

DWORD lReadTimeout, // таймаут операции чтения

LPSECURITY_ATTRIBUTES lpSecurityAttributes // опции наследования и

); // безопасности


BOOL FAR PASCAL Makeslot(HWND hwnd, HDC hdc)

{

LPSTR lpszSlotName = "\\\\.\\mailslot\\sample_mailslot";



// Дескриптор почтового ящика "hSlot1" определен глобально.

hSlot1 = CreateMailslot(lpszSlotName,

0, // без максимального размера сообщения

MAILSLOT_WAIT_FOREVER, // без таймаута при операциях

(LPSECURITY_ATTRIBUTES) NULL); // без атрибутов безопасности

if (hSlot1 == INVALID_HANDLE_VALUE)

{

ErrorHandler(hwnd, "CreateMailslot"); // обработка ошибки



return FALSE;

}

TextOut(hdc, 10, 10, "CreateMailslot вызвана удачно.", 26);



return TRUE;

}

2. Запись сообщений в почтовый ящик.

Запись в mailslot производится аналогично записи в стандартный дисковый файл. Следующий код иллюстрирует как с помощью функций CreateFile, WriteFile и CloseHandle можно поместить сообщение в почтовый ящик.
LPSTR lpszMessage = "Сообщение для sample_mailslot в текущем домене.";

BOOL fResult;

HANDLE hFile;

DWORD cbWritten;

// С помощью функции CreateFile клиент открывает mailslot для записи сообщений

hFile = CreateFile("\\\\*\\mailslot\\sample_mailslot",

GENERIC_WRITE,

FILE_SHARE_READ, // Требуется для записи в mailslot

(LPSECURITY_ATTRIBUTES) NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

(HANDLE) NULL);

if (hFile == INVALID_HANDLE_VALUE)

{

ErrorHandler(hwnd, "Ошибка открытия почтового ящика");



return FALSE;

}

// Запись сообщения в почтовый ящик



fResult = WriteFile(hFile,

lpszMessage,

(DWORD) lstrlen(lpszMessage) + 1, // включая признак конца строки

&cbWritten,

(LPOVERLAPPED) NULL);

if (!fResult)

{

ErrorHandler(hwnd, "Ошибка при записи сообщения");



return FALSE;

}

TextOut(hdc, 10, 10, "Сообщение отправлено успешно.", 21);



fResult = CloseHandle(hFile);

if (!fResult)

{

ErrorHandler(hwnd, "Ошибка при закрытии дескриптора");



return FALSE;

}

TextOut(hdc, 10, 30, "Дескриптор закрыт успешно.", 23);



return TRUE;
3. Чтение сообщений из почтового ящика.

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

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

BOOL GetMailslotInfo(

HANDLE hMailslot, // дескриптор почтового ящика

LPDWORD lpMaxMessageSize, // максимальный размер сообщения

LPDWORD lpNextSize, // размер следующего непрочитанного сообщения

LPDWORD lpMessageCount, // количество сообщений

LPDWORD lpReadTimeout // таймаут операции чтения

);
Функция устанавливает таймаут операции чтения:



BOOL SetMailslotInfo(

HANDLE hMailslot, // дескриптор почтового ящика

DWORD lReadTimeout // новый таймаут операции чтения

);
BOOL WINAPI Readslot(HWND hwnd, HDC hdc)

{

DWORD cbMessage, cMessage, cbRead;



BOOL fResult;

LPSTR lpszBuffer;

CHAR achID[80];

DWORD cAllMessages;

HANDLE hEvent;

OVERLAPPED ov;

cbMessage = cMessage = cbRead = 0;
hEvent = CreateEvent(NULL, FALSE, FALSE, "ExampleSlot");

ov.Offset = 0;

ov.OffsetHigh = 0;

ov.hEvent = hEvent;

// Дескриптор почтового ящика "hSlot1" определен глобально.

fResult = GetMailslotInfo(hSlot1, // дескриптор mailslot’а

(LPDWORD) NULL, // без ограничения размера сообщения

&cbMessage, // размер следующего сообщения

&cMessage, // количество сообщений в ящике

(LPDWORD) NULL); // без таймаута чтения

if (!fResult)

{

ErrorHandler(hwnd, "Ошибка при получении информации о почтовом ящике");



return FALSE;

}

if (cbMessage == MAILSLOT_NO_MESSAGE)



{

TextOut(hdc, 10, 10, "Нет непрочитанных сообщений.", 20);

return TRUE;

}

cAllMessages = cMessage;



while (cMessage != 0) // Считываем все сообщения

{

// Создаем строку с номером сообщения.



wsprintf((LPSTR) achID,

"\nMessage #%d of %d\n", cAllMessages - cMessage + 1,

cAllMessages);

// Выделяем память для сообщения.

lpszBuffer = (LPSTR) GlobalAlloc(GPTR,

lstrlen((LPSTR) achID) + cbMessage);

lpszBuffer[0] = '\0';
// Считываем сообщение из почтового ящика

fResult = ReadFile(hSlot1,

lpszBuffer,

cbMessage,

&cbRead,

&ov);


if (!fResult)

{

ErrorHandler(hwnd, "Ошибка чтения сообщения");



GlobalFree((HGLOBAL) lpszBuffer);

return FALSE;

}

// Формируем строку с номером и текстом сообщения.



lstrcat(lpszBuffer, (LPSTR) achID);

// Выводим сообщение на экран.

MessageBox(hwnd,

lpszBuffer,

"Содержимое почтового ящика",

MB_OK);

GlobalFree((HGLOBAL) lpszBuffer);

fResult = GetMailslotInfo(hSlot1, // дексриптор почтового ящика

(LPDWORD) NULL, // размер сообщения не ограничен

&cbMessage, // размер следующего сообщения

&cMessage, // количество сообщения

(LPDWORD) NULL); // без таймаута чтения

if (!fResult)

{

ErrorHandler(hwnd, "Ошибка при получении информации о mailslot’е");



return FALSE;

}

}



return TRUE;

}

Каналы (pipe)


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

Безымянные (или анонимные) каналы позволяют связанным процессам передавать информацию друг другу. Обычно, безымянные каналы используются для перенаправления стандартного ввода/вывода дочернего процесса так, чтобы он мог обмениваться данными с родительским процессом. Чтобы производить обмен данными в обоих направлениях, вы должны создать два безымянных канала. Родительский процесс записывает данные в первый канал, используя его дескриптор записи, в то время как дочерний процесс считывает данные из канала, используя дескриптор чтения. Аналогично, дочерний процесс записывает данные во второй канал и родительский процесс считывает из него данные. Безымянные каналы не могут быть использованы для передачи данных по сети и для обмена между несвязанными процессами.

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

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



\\.\pipe\pipename
Если канал находится на удаленном компьютере, то вам потребуется NETBIOS-имя компьютера:

\\ComputerName\pipe\pipename
Клиентам и серверам для работы с каналами допускается использовать функции из следующего списка:


Функция

Описание

CallNamedPipe

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

ConnectNamedPipe

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

CreateNamedPipe

Создает экземпляр именованного канала и возвращает дескриптор для последующих операций с каналом.

CreatePipe

Создает безымянный канал.

DisconnectNamedPipe

Отсоединяет серверную сторону экземпляра именованного канала от клиентского процесса.

GetNamedPipeHandleState

Получает информацию о работе указанного именованного канала.

GetNamedPipeInfo

Извлекает свойства указанного именованного канала.

PeekNamedPipe

Копирует данные их именованного или безымянного канала в буфер без удаления их из канала.

SetNamedPipeHandleState

Устанавливает режим чтения и режим блокировки вызова функций (синхронный или асинхронный) для указанного именованного канала.

TransactNamedPipe

Комбинирует операции записи сообщения в канал и чтения сообщения из канала в одну сетевую транзакцию.

WaitNamedPipe

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

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

Рассмотрим пример синхронной работы с каналами (т.е. с использованием блокирующих вызовов функций работы с каналами).
1. Многопоточный сервер каналов. Синхронный режим работы.

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

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

#include

#include

#include

VOID InstanceThread(LPVOID);

VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD);

int xx = 0;

DWORD main(VOID)

{

BOOL fConnected;



DWORD dwThreadId;

HANDLE hPipe, hThread;

LPTSTR lpszPipename = "\\\\.\\pipe\\mynamedpipe";

// Главный цикл создает экземпляр именованного канала и

// затем ожидает подключение клиентов к нему. Когда происходит

// подключение, создается поток, производящий обмен данными

// с клиентом, а выполнение главного цикла продолжается.

for (;;)

{

hPipe = CreateNamedPipe(



lpszPipename, // Имя канала

PIPE_ACCESS_DUPLEX, // Дуплексный доступ к каналу

PIPE_TYPE_MESSAGE | // Установка режима работы канала

PIPE_READMODE_MESSAGE | // для передачи по нему отдельных сообщений

PIPE_WAIT, // Синхронное выполнение операций с каналом

PIPE_UNLIMITED_INSTANCES, // Неограниченное количество экземпляров

BUFSIZE, // Размер буфера отправки

BUFSIZE, // Размер буфера приема

PIPE_TIMEOUT, // Время ожидания клиента

NULL); // Без дополнительных атрибутов безопасности


if (hPipe == INVALID_HANDLE_VALUE)

MyErrExit("Экземпляр именованного канала не создан");

// Ждем, пока не подсоединится клиент; в случае успешного подключения,

// Функция возвращает ненулевое значение. Если функция вернет ноль,

// то GetLastError вернет значение ERROR_PIPE_CONNECTED.

fConnected = ConnectNamedPipe(hPipe, NULL) ?

TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

if (fConnected)

{

// Создаем поток для обслуживания клиента.



hThread = CreateThread(

NULL, // без атрибутов безопасности

0, // размер стека по умолчанию

(LPTHREAD_START_ROUTINE) InstanceThread,

(LPVOID) hPipe, // параметр потока – дескриптор канала

0, // без отложенного запуска

&dwThreadId); // возвращает дескриптор потока
if (hThread == NULL)

MyErrExit("Создание потока произошло с ошибками");

else

CloseHandle(hThread);



}

else


// Если клиент не смог подсоединиться, то уничтожаем экземпляр канала.

CloseHandle(hPipe);

}

return 1;



}
// Главная функция потока, обслуживающего клиентские подключения

VOID InstanceThread(LPVOID lpvParam)

{

CHAR chRequest[BUFSIZE];



CHAR chReply[BUFSIZE];

DWORD cbBytesRead, cbReplyBytes, cbWritten;

BOOL fSuccess;

HANDLE hPipe;

// Переданный потоку параметр интерпретируем как дескриптор канала.

hPipe = (HANDLE) lpvParam;

while (1)

{

// Считываем из канала запросы клиентов.



fSuccess = ReadFile(

hPipe, // дескриптор канала

chRequest, // буфер для получения данных

BUFSIZE, // указываем размер буфера

&cbBytesRead, // запоминаем количество считанных байт

NULL); // синхронный режим ввода-вывода


// Обрабатываем запрос если он корректен

if (! fSuccess || cbBytesRead == 0)

break;

GetAnswerToRequest(chRequest, chReply, &cbReplyBytes);



// Записываем в канал результат обработки клиентского запроса.

fSuccess = WriteFile(

hPipe, // дескриптор канала

chReply, // буфер с данными для передачи

cbReplyBytes, // количество байт для передачи

&cbWritten, // запоминаем количество записанных в канал байт

NULL); // синхронный режим ввода-вывода
if (! fSuccess || cbReplyBytes != cbWritten) break;

}

// Записываем содержимое буферов в канал, чтобы позволить клиенту считать



// остаток информации перед отключением. Затем выполним отсоединение от

// канала и уничтожаем дескриптор этого экземпляра канала.

FlushFileBuffers(hPipe);

DisconnectNamedPipe(hPipe);

CloseHandle(hPipe);

}
2. Клиент каналов. Синхронный режим работы.

В данном примере клиент открывает именованный канал с помощью функции CreateFile и устанавливает канал в режим чтения/записи сообщений с помощью функции SetNamedPipeHandleState. Затем использует функции WriteFile и ReadFile для отправки запросов серверу и чтения ответов сервера соответственно.
#include

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

{

HANDLE hPipe;



LPVOID lpvMessage;

CHAR chBuf[512];

BOOL fSuccess;

DWORD cbRead, cbWritten, dwMode;

LPTSTR lpszPipename = "\\\\.\\pipe\\mynamedpipe";

// Пытаемся открыть именованный канал; если необходимо, то подождем.

while (1)

{

hPipe = CreateFile(



lpszPipename, // имя канала

GENERIC_READ | // доступ на чтение и запись данных

GENERIC_WRITE,

0, // без разделения доступа

NULL, // без дополнительных атрибутов безопасности

OPEN_EXISTING, // открываем существующий канал

0, // задаем атрибуты по умолчанию

NULL); // без файла шаблона

// Если подключение успешно, то выходим из цикла ожидания.

if (hPipe != INVALID_HANDLE_VALUE)

break;

// Если возникает ошибка отличная от ERROR_PIPE_BUSY, то прекращаем работу.



if (GetLastError() != ERROR_PIPE_BUSY)

MyErrExit("Не могу открыть канал");

// Ждем 20 секунд до повторного подключения.

if (! WaitNamedPipe(lpszPipename, 20000) )

MyErrExit("Не могу открыть канал");

}

// Канал подключен успешно; сменим режим работы канала на чтение/запись сообщений



dwMode = PIPE_READMODE_MESSAGE;

fSuccess = SetNamedPipeHandleState(

hPipe, // дескриптор канала

&dwMode, // новый режим работы

NULL, // не устанавливаем максимальный размер

NULL); // не устанавливаем максимальное время

if (!fSuccess)

MyErrExit("Невозможно сменить режим работы канала");

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

lpvMessage = (argc > 1) ? argv[1] : "default message";

fSuccess = WriteFile(

hPipe, // дескриптор канала

lpvMessage, // сообщение

strlen(lpvMessage) + 1, // длина сообщения

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

NULL); // синхронный ввод/вывод

if (! fSuccess)

MyErrExit("Запись сообщения в канал не удалась");

do

{

// Считываем сообщения за канала.



fSuccess = ReadFile(

hPipe, // дескриптор канала

chBuf, // буфер для получения ответа

512, // размер буфера

&cbRead, // количество считанных из канала байт

NULL); // синхронный ввод/вывод

if (! fSuccess && GetLastError() != ERROR_MORE_DATA)

break;


// Следующий код – код обработки ответа сервера.

// В данном случае просто выводим сообщение на STDOUT

if (! WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),

chBuf, cbRead, &cbWritten, NULL))

break;
} while (! fSuccess); // если ERROR_MORE_DATA (т.е. еще остались данные),

// то повторяем считывание из канала

// Закрываем канал

CloseHandle(hPipe);

return 0;

}
3. Совмещенное чтение/запись данных при работе с каналом.

Транзакции в именованных каналах — это клиент–серверные коммуникации, объединяющие операции записи и чтения в одну сетевую операцию. Такие транзакции погут быть использованы только на дуплексных, ориентированных на сообщения, каналах. Совмещенное чтение/запись данных позволяет увеличить производительность канала между клиентом и удаленным сервером. Процессы могут использовать функции TransactNamedPipe и CallNamedPipe для организации транзакций.

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


fSuccess = TransactNamedPipe(

hPipe, // дескриптор канала

lpszWrite, // сообщение серверу

strlen(lpszWrite)+1, // длина сообщения серверу

chReadBuf, // буфер для получения ответа

512, // размер буфера ответа

&cbRead, // количество считанных байт

NULL); // синхронный вызов функции

// Если возникла ошибка, то выходим из функции иначе выводим ответ сервера

// и, если необходимо, считываем оставшиеся данные из канала

if (!fSuccess && (GetLastError() != ERROR_MORE_DATA))

{

MyErrExit("Ошибка при выполнении транзакции");



}

while(1)

{

// Направляем на STDOUT считанные из канала данные.



if (! WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),

chReadBuf, cbRead, &cbWritten, NULL) )

break;

// Если все операции прошли успешно, то выходим из цикла.



if (fSuccess)

break;


// Если в канале еще остались данные, то считываем их.

fSuccess = ReadFile(

hPipe, // дескриптор канала

chReadBuf, // буфер для получения ответа

512, // размер буфера

&cbRead, // количество считанных байт

NULL); // синхронный вызов функции

// Если возникла ошибка отличная от ERROR_MORE_DATA, то прекращаем работу.

if (! fSuccess && (GetLastError() != ERROR_MORE_DATA))

break;


}
Следующий код показывает вызов функции CallNamedPipe клиентом каналов.
// Комбинирует операции соединения, ожидания, записи, чтения и закрытия канала.

fSuccess = CallNamedPipe(

lpszPipename, // дескриптор канала

lpszWrite, // сообщение серверу

strlen(lpszWrite)+1, // длина сообщения серверу

chReadBuf, // буфер для получения ответа

512, // размер буфера для получения ответа

&cbRead, // количество считанных байт

20000); // ждем 20 секунд

if (fSuccess || GetLastError() == ERROR_MORE_DATA)

{

// В случае успеха выводим данные на STDOUT.



WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),

chReadBuf, cbRead, &cbWritten, NULL);

// Канал уже закрыт, следовательно оставшиеся в нем данные

// прочесть уже невозможно – они безвозвратно потеряны.

if (! fSuccess)

printf("\n...дополнительные данные в сообщении потеряны\n");



}

СОДЕРЖАНИЕ ОТЧЕТА


  1. Наименование лабораторной работы, ее цель.

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

Таблица 1.Задание на лабораторную работу

№ варианта

Способ

Задача

1, 16

отображение файлов

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

2 , 21

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

3, 26

каналы

4, 17

отображение файлов

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

5 , 22

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

6, 27

каналы

7, 18

отображение файлов

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

8 , 23

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

9, 28

каналы

10, 19

отображение файлов

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

11, 24

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

12, 29

каналы

13, 20

отображение файлов

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

14, 25

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

15, 30

каналы



  1. Примеры разработанных приложений (программы и результаты).






Смотрите также:
Межпроцессное взаимодействие англ. Inter-Process Communication, ipc
76.23kb.
1 стр.
Лабораторная работа №6 Межпроцессное взаимодействие
279.25kb.
1 стр.
Лабораторная работа по химии, физике, биологии, т е. по естественно-научным предметам. На уроках русского языка и литературы термин «лабораторная работа»
261.84kb.
1 стр.
Лабораторная работа №5 Лабораторная работа выполняется согласно выбранной теме курсовой работы!!! Количество таблиц в бд: от 4 до 6
46.6kb.
1 стр.
Лабораторная работа №1 Построение детерминированного синтаксического анализатора
278.71kb.
1 стр.
Лабораторная работа №1 Установка и настройка сетевой карты. Лабораторная работа №2 Восстановление компьютера после сбоя.
58.29kb.
1 стр.
Лабораторная работа Введение в разработку Winrt-приложений на html/JavaScript
473.51kb.
4 стр.
Лабораторная работа №1 по курсу "Информационная безопасность" Лабораторная работа №1
118.45kb.
1 стр.
Лабораторная работа 4 взаимодействие γ-излучения
175.2kb.
1 стр.
Лабораторная работа 9-01 Лабораторная Шонин В. А. работа 9-01 Использование табличной верстки для создания Web-страниц
446.97kb.
1 стр.
Лабораторная работа Электронная цифровая подпись (эцп). Работа с программой pgp
48.31kb.
1 стр.
Методические указания и лабораторные задания по курсу основы информатики и
933.18kb.
11 стр.