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




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

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

Почтовые ящики (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 <stdio.h> #include <stdlib.h> #include <string.h> #include <windows.h> 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 <windows.h> 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 каналы

 

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

 

 

 

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-03-27 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: