Лабораторные работы / Windows лаб 1-4 / lab2_var2_otchet
.pdfОтчет по лабораторной работе № 2 по дисциплине «Системное программное обеспечение»
на тему: «Межпроцессорное взаимодействие»
Вариант 2
Написать пару программ, взаимодействующих через именованные каналы. Программасервер хранит строки-значения по строкам-ключам. Программа-клиент, отправляя программесерверу команды, добавляет, удаляет и получает значения.
Программа-сервер:
Объявления и включения в начале программы: #include <windows.h>
#include <iostream> #include <sstream> #include <string> #include <map>
#define MAX_BUFFER_SIZE 64
using dictionary_t = std::map<std::string, std::string>;//Для данных ключ-значение
1. Запрашиваем у пользователя имя канала и создаем дуплексный, ориентированный на сообщения канал с заданным именем функцией CreateNamedPipe().
HANDLE StartServer()
{
std::string pipe_name; //Запрос имени канала
std::cout << "Enter pipe name: "; std::getline(std::cin, pipe_name);
auto path = "\\\\.\\pipe\\" + pipe_name;
//Создание дуплексного, ориентированного на сообщения канала return CreateNamedPipe(
path.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, MAX_BUFFER_SIZE, MAX_BUFFER_SIZE,
0,
nullptr);
}
2.Ожидаем подключения клиента к каналу функцией ConnectNamedPipe().Если успешного подключения не произошло, выводим код ошибки и закрываем канал. Иначе выводим сообщение о соединении.
//Ожидание подключения клиента bool fconnected(HANDLE pipe)
{
std::cout << "Waiting for client... ";
if (!ConnectNamedPipe(pipe, nullptr))
{
auto err = GetLastError();
if (err != ERROR_PIPE_CONNECTED)
{
std::cout << "Error. Error code: " << err <<'\n'; CloseHandle(pipe);
return false;
}
}
std::cout << "connected.\n"; return true;
}
3.Считываем из подключенного канала одну строку-команду, состоящую из имени и аргументов, разделенных пробелами (ни в имени, ни в аргументах пробелов нет) функцией ReadFile(). Если клиент разорвал соединение или произошла другая ошибка чтения, выводим соответствующее сообщение. Код для всех подпунктов 3 содержится в функции
void HandleConnection(HANDLE pipe, dictionary_t& dict). std::cout << "Waiting for command... ";
2
char msg_buffer[MAX_BUFFER_SIZE]; DWORD read = 0;
//Чтение запроса клиента
auto fsuccess = ReadFile(pipe, msg_buffer, MAX_BUFFER_SIZE, &read, nullptr); if (!fsuccess)
{
auto err = GetLastError();
if (err == ERROR_BROKEN_PIPE)//Если клиент разорвал соединение std::cout << "client disconnected.\n";
else
std::cout << "Error. Error code: " << err <<'\n'; DisconnectNamedPipe(pipe);
break;
}
msg_buffer[read] = 0;
std::cout << "received: '" << msg_buffer << "'.\n"; std::stringstream stream{msg_buffer};
std::string cmd; stream >> cmd;
Объявляем константы, которые будем использовать в пунктах 3.1-3.5: std::string ack = "acknowledged";
std::string del = "deleted"; std::string found = "found "; std::string miss = "missing";
std::string wrong_cmd = "Wrong command! Commands: get, set, list, delete or quit.";
3.1. Выполняем сохранение значения по ключу. Формат команды: set ключ значение. Сохраняем в памяти значение под указанным ключом и записываем в канал строку acknowledged.
DWORD written;
//Сохранение значения по ключу if (cmd == "set")
{
std::string key, value;
3
stream >> key >> value; dict[key] = value;
WriteFile(pipe, ack.c_str(), ack.size(), &written, nullptr);
}
3.2.Получаем значение по ключу. Формат команды: get ключ. Если ключ имеется в хранилище, записываем в канал строку в формате: found значение. В противном случае записываем в канал строку missing.
//Получение значения по ключу else if (cmd == "get")
{
std::string key; stream >> key;
auto it = dict.find(key);
if (it != dict.end())//Если ключ есть в хранилище
{
auto answer = "found " + it->second;
WriteFile(pipe, answer.c_str(), answer.size(), &written, nullptr);
}
else//Если ключа нет в хранилище
{
WriteFile(pipe, miss.c_str(), miss.size(), &written, nullptr);
}
}
3.3.Получаем список ключей в хранилище. Формат команды: list. Записываем в канал строку, содержащую через пробел все имеющиеся в хранилище ключи.
//Получение списка ключей в хранилище else if (cmd == "list")
{
std::string answer;
for (const auto& kv : dict) answer += kv.first + ' ';
WriteFile(pipe, answer.c_str(), answer.size(), &written, nullptr);
4
}
3.4. Удаляем значение под заданным ключом. Формат команды: delete ключ. Если ключ присутствует в хранилище, записываем в канал строку deleted, иначе — строку missing.
//Удаление значения под заданным ключом else if (cmd == "delete")
{
std::string key; stream >> key;
auto it = dict.find(key);
if (it != dict.end())//Если ключ есть в хранилище
{
dict.erase(it);
WriteFile(pipe, del.c_str(), del.size(), &written, nullptr);
}
else//Если ключа нет в хранилище
{
WriteFile(pipe, miss.c_str(), miss.size(), &written, nullptr);
}
}
Если пользователь ввел неверную команду, указываем на ошибку. //Если неверная команда
else
{
WriteFile(pipe, wrong_cmd.c_str(), wrong_cmd.size(), &written, nullptr);
}
3.5. Прекращаем сеанс связи. Формат команды: quit. Отключаем именованный канал от клиента функцией DisconnectNamedPipe().
//Прекращение сеанса связи if (cmd == "quit")
{
DisconnectNamedPipe(pipe);
5
break;
}
4. Переходим к пункту 3 (цикл работы с подключенным клиентом).
Пункты 3.1-3.5 выполняются в бесконечном цикле, который прерывается, когда пользователь вводит команду quit.
5.Запрашиваем у пользователя, следует ли остановить сервер. В случае утвердительного ответа уничтожаем именованный канал, созданный на шаге 1, функцией СloseHandle().
//Запрос у пользователя, следует ли остановить сервер bool StopServer()
{
while (true)
{
std::cout << "Stop server [y/n]? "; int curr = std::cin.get();
if (curr == 'y') return true; if (curr == 'n') return false;
std::cout << "Wrong input. Please enter 'y' for 'yes' or 'n' for 'no'.\n";
}
}
6.Переходим к пункту 2 (цикл ожидания клиентов и работы с ними).
Переход осуществляется в главной программе при помощи бесконечного цикла, который прерывается, только если пользователь ответил утвердительно на вопрос об остановке сервера. int main()
{
auto pipe = StartServer();
if (pipe == INVALID_HANDLE_VALUE)
{
std::cout << "Error. Error code: " << GetLastError()<<'\n'; return EXIT_FAILURE;
6
}
dictionary_t data; while (true)
{
if (!fconnected(pipe)) return EXIT_FAILURE;
HandleConnection(pipe, data); if (StopServer())
break;
}
CloseHandle(pipe);//Закрытие канала return EXIT_SUCCESS;
}
Программа-клиент:
Объявления и включения в начале программы: #include <windows.h>
#include <iostream> #include <sstream> #include <string>
#define MAX_BUFFER_SIZE 64
Все действия выполняются в главной программе.
1. Запрашиваем у пользователя имя канала и подключаемся как клиент к указанному каналу функцией CreateFile(). Если не удалось подключиться, закрываем канал, выводим код ошибки и завершаем программу.
//Запрос имени канала std::string pipe_name;
std::cout << "Enter pipe name: "; std::getline(std::cin, pipe_name);
auto pipe_path = "\\\\.\\pipe\\" + pipe_name; //Подключение к указанному каналу
auto pipe = CreateFile(pipe_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
7
if (pipe != INVALID_HANDLE_VALUE)
{
DWORD dwmode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
auto fsuccess = SetNamedPipeHandleState(pipe, &dwmode, nullptr, nullptr); if (!fsuccess)
{
CloseHandle(pipe);
pipe = INVALID_HANDLE_VALUE;
}
}
if (pipe == INVALID_HANDLE_VALUE)
{
std::cout << "Error. Error code: " << GetLastError()<<'\n'; return EXIT_FAILURE;
}
2.Запрашиваем у пользователя строку-команду и записываем её в открытый канал функцией WriteFile().
std::string buf; DWORD written;
//Запрос у пользователя строки-команды std::cout << "> ";
std::getline(std::cin, buf);
WriteFile(pipe, buf.c_str(), buf.size(), &written, nullptr);//Запись строки в канал
3.Если на шаге 2 была введена команда quit, закрываем канал функцией CloseHandle() и завершаем работу.
//Переход к закрытию канала if (buf == "quit")
break;
После бесконечного цикла, выход из которого производится по команде quit, следует закрытие канала:
CloseHandle(pipe);//Закрытие канала
8
4.Считываем из канала ответ функцией ReadFile() и отображаем его на экране. DWORD read;
char msg_buffer[MAX_BUFFER_SIZE]; //Считывание ответа сервера
ReadFile(pipe, msg_buffer, MAX_BUFFER_SIZE, &read, nullptr); msg_buffer[read] = 0;
std::cout << msg_buffer << '\n';//Отображение ответа сервера
5.Переходим к шагу 2 (цикл).
Вбесконечный цикл входят команды пунктов 2-4. Выход из цикла производится по команде quit.
Пример работы программ
9