Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Готовые отчеты / ОСиС. Лабораторная работа 8

.pdf
Скачиваний:
9
Добавлен:
21.11.2020
Размер:
291.07 Кб
Скачать

Федеральное агентство связи ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ

ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ «САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ. ПРОФ. М. А. БОНЧ-БРУЕВИЧА» (СПбГУТ)

Факультет инфокоммуникационных сетей и систем Кафедра программной инженерии и вычислительной техники

ЛАБОРАТОРНАЯ РАБОТА №8 по дисциплине «Операционные системы и сети»

на тему «Разработка многопроцессной программы под Windows»

Выполнил: студент 3-го курса дневного отделения группы ИКПИ-85

Коваленко Леонид Александрович Преподаватель:

доцент кафедры ПИиВТ Дагаев Александр Владимирович

Санкт-Петербург 2020

Цель работы Разработать многопроцессную программу с применением механизмов

синхронизации процессов под Windows. Постановка задачи

1.Написать программу на Си для односторонней передачи данных при помощи пайпа и продемонстрировать ее работу.

2.Написать программу на Си для двусторонней передачи данных при помощи пайпов и продемонстрировать ее работу.

Ход работы

Работа выполняется в операционной системе MS Windows 10 Pro. Напишем программу на Си для односторонней передачи данных при

помощи пайпа (табл. 1).

Таблица 1 — Файл main.c (односторонняя передача данных)

#include <stdio.h> #include <string.h> #include <Windows.h>

#define NAMEDPIPE_NAME "\\\\.\\pipe\\spec_pipe" #define BUFSIZE 50

int main() {

char buffer[BUFSIZE];

// Попытка создания именнованого пайпа

HANDLE hPipe = CreateNamedPipe(TEXT(NAMEDPIPE_NAME), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, sizeof(char) * BUFSIZE, sizeof(char) * BUFSIZE, NMPWAIT_USE_DEFAULT_WAIT, NULL);

DWORD dwRead;

// Если канал был создан и ошибок не произошло if (GetLastError() == 0) {

while (1) {

printf("Ожидаем клиента...\n"); // Ждем очередного подключения

if (ConnectNamedPipe(hPipe, NULL)) {

// До тех пор, пока соединение не оборвалось while (1) {

memset(buffer, '\0', BUFSIZE); // Очистка printf("Введите сообщение, stop или exit: "); fgets(buffer, BUFSIZE, stdin);

if (strncmp(buffer, "stop", sizeof(char) * 4) == 0) { DisconnectNamedPipe(hPipe);

break;

}

if (strncmp(buffer, "exit", sizeof(char) * 4) == 0) { DisconnectNamedPipe(hPipe);

CloseHandle(hPipe); return 0;

}

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

if (!WriteFile(hPipe, buffer, BUFSIZE, NULL, NULL)) { printf("Клиент покинул канал.\n");

break;

2

}

printf("Отправлено: %s", buffer);

}

}

DisconnectNamedPipe(hPipe);

}

return 0;

}

// Если канал уже был создан, то подключаемся к нему if (GetLastError() == 183) {

hPipe = CreateFile(TEXT(NAMEDPIPE_NAME), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE) { printf("ERROR %lu\n", GetLastError());

return -1;

}

printf("Получаем сообщения...\n"); while (1) {

memset(buffer, '\0', BUFSIZE); // Очистка // Получаем сообщение

if (!ReadFile(hPipe, buffer, BUFSIZE, &dwRead, NULL)) { return 0;

}

buffer[dwRead] = '\0'; printf("Получено: %s", buffer);

}

return 0;

}

printf("ERROR %lu\n", GetLastError()); return -1;

}

Продемонстрируем работу программы (рис. 1).

Рисунок 1 — Процесс передачи данных левым процессом правому Сначала левый процесс создает пайп и ожидает правого. Правый

подключается. Затем левый процесс пишет сообщения, а правый читает. Если левый процесс пишет stop или exit, то соединение прерывается. Команда stop используется для прерывания соединения с текущим клиентом и ожидания нового. Команда exit — для выхода из программы.

3

Напишем программу на Си для двусторонней передачи данных при помощи пайпов и потоков (табл. 2).

Таблица 2 — Файл main.c (двусторонняя передача данных)

#include <stdio.h> #include <string.h> #include <Windows.h>

#define NAMEDPIPE1_NAME "\\\\.\\pipe\\spec_pipe_one" #define NAMEDPIPE2_NAME "\\\\.\\pipe\\spec_pipe_two" #define BUFSIZE 50

//Функция симуляции ввода Enter в поток ввода stdin void sendEnterToStdin() {

DWORD dw; INPUT_RECORD ir[2];

for (int i = 0; i < 2; i++) {

KEY_EVENT_RECORD *kev = &ir[i].Event.KeyEvent; ir[i].EventType = KEY_EVENT;

kev->bKeyDown = i == 0; kev->dwControlKeyState = 0; kev->wRepeatCount = 1; kev->uChar.UnicodeChar = VK_RETURN; kev->wVirtualKeyCode = VK_RETURN;

kev->wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);

}

WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dw);

}

//Функция потока записи сервера

DWORD WINAPI runWriteServerThread(LPVOID * arg) { HANDLE hPipe = *(HANDLE *)arg;

char buffer[BUFSIZE]; while (1) {

memset(buffer, '\0', BUFSIZE); printf("#> ");

fgets(buffer, BUFSIZE, stdin);

if (strncmp(buffer, "stop", sizeof(char) * 4) == 0) { break;

}

if (strncmp(buffer, "exit", sizeof(char) * 4) == 0) { _exit(0);

}

if (!WriteFile(hPipe, buffer, BUFSIZE, NULL, NULL)) {

if (GetLastError() == 232 || GetLastError() == 233) { printf("Клиент прервал соединение.\n");

}

else {

printf("WriteThread ERROR %lu\n", GetLastError());

}

fflush(stdout); break;

}

printf("Отправлено: %s", buffer); fflush(stdout);

}

return 0;

}

// Функция потока записи клиента

DWORD WINAPI runWriteClientThread(LPVOID * arg) { HANDLE hPipe = *(HANDLE *)arg;

char buffer[BUFSIZE]; while (1) {

4

memset(buffer, '\0', BUFSIZE); printf("#> ");

fgets(buffer, BUFSIZE, stdin); if (*buffer != '\n') {

if (strncmp(buffer, "exit", sizeof(char) * 4) == 0) { _exit(0);

}

if (!WriteFile(hPipe, buffer, BUFSIZE, NULL, NULL)) {

if (GetLastError() == 232 || GetLastError() == 233) { printf("Сервер прервал соединение.\n");

}

else {

printf("WriteThread ERROR %lu\n", GetLastError());

}

fflush(stdout); return -1;

}

printf("Отправлено: %s", buffer); fflush(stdout);

}

}

}

//Функция потока записи сервера/клиента DWORD WINAPI runReadThread(LPVOID * arg) {

HANDLE hPipe = *(HANDLE *)arg;

DWORD dwRead;

char buffer[BUFSIZE]; while (1) {

memset(buffer, '\0', BUFSIZE);

if (!ReadFile(hPipe, buffer, BUFSIZE, &dwRead, NULL)) { if (GetLastError() == 109 || GetLastError() == 233) {

printf("\nСоединение прервано.\n");

}

else {

printf("\nReadThread ERROR %lu\n", GetLastError());

}

fflush(stdout);

sendEnterToStdin(); return -1;

}

buffer[dwRead] = '\0'; printf("\nПолучено: %s#> ", buffer); fflush(stdout);

}

}

//Функция создания пайпа с записью со стороны сервера

HANDLE createPipeFromServer() {

return CreateNamedPipe(TEXT(NAMEDPIPE1_NAME), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, sizeof(char) * BUFSIZE, sizeof(char) * BUFSIZE, NMPWAIT_USE_DEFAULT_WAIT, NULL);

}

// Функция создания пайпа с записью со стороны клиента HANDLE createPipeFromClient() {

return CreateNamedPipe(TEXT(NAMEDPIPE2_NAME), PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, sizeof(char) * BUFSIZE, sizeof(char) * BUFSIZE, NMPWAIT_USE_DEFAULT_WAIT, NULL);

}

int main() {

// Попытка создания пайпа с записью со стороны сервера

HANDLE hPipeFromServer = createPipeFromServer(), hPipeFromClient;

5

// Если канал был успешно создан и ошибок не произошло if (GetLastError() == 0) {

while (1) {

//Создаем второй пайп с записью со стороны клиента hPipeFromClient = createPipeFromClient();

if (GetLastError() != 0) {

printf("ERROR %lu\n", GetLastError()); return 1;

}

printf("Ожидаем клиента...\n"); fflush(stdout);

//Ждем очередного подключения

if (ConnectNamedPipe(hPipeFromServer, NULL)) { // До тех пор, пока соединение не оборвалось

printf("Отправьте сообщения / прервите соединение (stop) / выход (exit)\n");

fflush(stdout);

HANDLE hWriteThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &runWriteServerThread, &hPipeFromServer, 0, NULL);

HANDLE hReadThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &runReadThread, &hPipeFromClient, 0, NULL);

WaitForSingleObject(hWriteThread, INFINITE); TerminateThread(hReadThread, 0); CloseHandle(hWriteThread); CloseHandle(hReadThread);

FlushFileBuffers(hPipeFromServer);

DisconnectNamedPipe(hPipeFromServer);

DisconnectNamedPipe(hPipeFromClient);

CloseHandle(hPipeFromClient);

}

}

return 0;

}

// Если канал уже был создан, то подключаемся к нему if (GetLastError() == 183) {

hPipeFromServer = CreateFile(TEXT(NAMEDPIPE1_NAME), GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);

hPipeFromClient = CreateFile(TEXT(NAMEDPIPE2_NAME), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);

if (hPipeFromServer == INVALID_HANDLE_VALUE || hPipeFromClient == INVALID_HANDLE_VALUE) {

if (GetLastError() == 231) { printf("Все пайпы заняты."); return 2;

}

printf("ERROR %lu\n", GetLastError()); return 3;

}

printf("Отправьте сообщения / прервите соединение (exit)\n"); fflush(stdout);

HANDLE hWriteThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &runWriteClientThread, &hPipeFromClient, 0, NULL);

HANDLE hReadThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &runReadThread, &hPipeFromServer, 0, NULL);

WaitForSingleObject(hWriteThread, INFINITE); TerminateThread(hReadThread, 0);

return 0;

}

// Если ничего не удалось, печатаем ошибку printf("ERROR %lu\n", GetLastError()); return 4;

}

Продемонстрируем работу программы (рис. 2).

6

Рисунок 2 — Процесс коммуникации двух процессов Сначала левый процесс создает пайпы и ожидает правого. Правый

подключается. Затем оба читают и пишут друг другу сообщения. Если левый процесс пишет stop или exit, то соединение прерывается. Команда stop используется для прерывания соединения с текущим клиентом и ожидания нового. Команда exit — для выхода из программы. При попытке третьего процесса подключиться к занятым пайпам выводится ошибка «Все пайпы заняты.».

Важно понимать, что пайпы (каналы) в Windows, в отличие от Linux, смещены к взаимодействию «клиент-сервер» и работают отчасти подобно сокетам. Но все же клиент-серверное взаимодействие лучше осуществлять при помощи сокетов. Пайпы скорее для конвейерной обработки.

В Windows, в отличие от Linux, имеется понятие двустороннего пайпа. На самом деле понятно, что пайп — это труба с двумя концами: в один момент времени только один может писать, а другой читать. Другое дело — если писатель и читатель меняются местами. Тогда двусторонний пайп — то, что нужно. Но если нужно чтение и запись с обоих сторон без специальной логики по смене мест, то потребуется два пайпа — с записью со стороны сервера и с записью со стороны клиента (как в примере).

Заключение В результате выполнения лабораторной работы мы ознакомились с

применением механизмов синхронизации процессов под Windows.

7