Лабораторные работы / Windows лаб 1-4 / lab2_var2_kod
.pdfВариант 2
Задание
Написать пару программ, взаимодействующих через именованные каналы. Программа-сервер хранит строки-значения по строкам-ключам. Программа-клиент, отправляя программе-серверу команды, добавляет, удаляет и получает значения.
Программа-сервер:
1.Запрашивает у пользователя имя канала и создает дуплексный, ориентированный на сообщения канал с заданным именем функцией CreateNamedPipe().
2.Ожидает подключения клиента к каналу функцией ConnectNamedPipe().
3.Считывает из подключенного канала одну строку-команду, состоящую из имени и аргументов, разделенных пробелами (ни в имени, ни в аргументах пробелов нет) функцией ReadFile() и выполняет полученную команду:
3.1.Сохранить значение по ключу. Формат команды: set ключ значение
Необходимо сохранить в памяти значение под указанным ключом и записать в канал строку acknowledged.
3.2.Получить значение по ключу. Формат команды: get ключ
Если ключ имеется в хранилище, следует записать в канал строку в формате found значение
В противном случает следует записать в канал строку missing. Указание. Запись можно также выполнить функцией WriteFile () .
3.3.Получить список ключей в хранилище. Формат команды:
list
Необходимо записать в канал строку, содержащую через пробел все имеющиеся в хранилище ключи.
3.4.Удалить значение под заданным ключом. Формат команды: delete ключ
Если ключ присутствует в хранилище, следует записать в канал строку deleted, иначе — строку missing.
3.5.Прекратить сеанс связи. Формат команды:
quit
Необходимо отключить именованный канал от клиента функцией DisconnectNamedPipe().
4.Переходит к пункту 3 (цикл работы с подключенным клиентом).
5.Запрашивает у пользователя, следует ли остановить сервер.
В случае утвердительного ответа следует уничтожить именованный канал, созданный
на шаге 1, функцией CloseHandle().
6. Переходит к пункту 2 (цикл ожидания клиентов и работы с ними).
Программа-клиент:
1.Запрашивает у пользователя имя канала и подключается как клиент к указанному каналу функцией
CreateFile().
2.Запрашивает у пользователя строку-команду и записывает её в открытый канал функцией WriteFile().
3.Если на шаге 2 была введена команда quit, закрывает канал функцией CloseHandle() и завершает работу.
4.Считывает из канала ответ функцией ReadFile() и отображает его на экране.
5.Переходит к шагу 2 (цикл).
Указание 1. Хранение значений по ключам в C++ удобно реализовать с std::map , а вычленение имени команды и аргументов из строки — классом std::stringstream .
Указание 2. При отладке первой из двух программ целесообразно использовать образец
решения второй в качестве недостающей части. Раздел MSDN об именованных каналах также содержит пример, близкий к программе-клиенту: https://docs.microsoft.com/ru-ru/windows/win32/ipc/named-pipe-client?redirectedfrom=MSDN
Проверку работы программ следует выполнять, запуская несколько экземпляров приложения, например, из «Проводника».
1
Выполнение
Сервер (server.cpp):
#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>;//Для данных ключ-значение
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);
}
//Ожидание подключения клиента 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;
}
//Выполнение запросов клиента
void HandleConnection(HANDLE pipe, dictionary_t& dict)
{
2
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.";
while (true)
{
std::cout << "Waiting for command... ";
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;
//Прекращение сеанса связи if (cmd == "quit")
{
DisconnectNamedPipe(pipe);
break;
}
DWORD written;
//Сохранение значения по ключу if (cmd == "set")
{
std::string key, value; stream >> key >> value; dict[key] = value;
WriteFile(pipe, ack.c_str(), ack.size(), &written, nullptr);
}
//Получение значения по ключу else if (cmd == "get")
{
std::string key; stream >> key;
auto it = dict.find(key);
if (it != dict.end())//Если ключ есть в хранилище
{
3
auto answer = "found " + it->second;
WriteFile(pipe, answer.c_str(), answer.size(), &written, nullptr);
}
else//Если ключа нет в хранилище
{
WriteFile(pipe, miss.c_str(), miss.size(), &written, nullptr);
}
}
//Получение списка ключей в хранилище else if (cmd == "list")
{
std::string answer;
for (const auto& kv : dict) answer += kv.first + ' ';
WriteFile(pipe, answer.c_str(), answer.size(), &written, nullptr);
}
//Удаление значения под заданным ключом 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);
}
}
}
//Запрос у пользователя, следует ли остановить сервер 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";
4
}
}
int main()
{
auto pipe = StartServer();
if (pipe == INVALID_HANDLE_VALUE)
{
std::cout << "Error. Error code: " << GetLastError()<<'\n'; return EXIT_FAILURE;
}
dictionary_t data; while (true)
{
if (!fconnected(pipe)) return EXIT_FAILURE;
HandleConnection(pipe, data); if (StopServer())
break;
}
CloseHandle(pipe);//Закрытие канала return EXIT_SUCCESS;
}
Client.cpp:
#include <windows.h> #include <iostream> #include <sstream> #include <string>
#define MAX_BUFFER_SIZE 64
int main()
{
//Запрос имени канала 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);
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)
5
{
std::cout << "Error. Error code: " << GetLastError()<<'\n'; return EXIT_FAILURE;
}
std::string buf; DWORD written, read; while (true)
{
//Запрос у пользователя строки-команды std::cout << "> ";
std::getline(std::cin, buf);
WriteFile(pipe, buf.c_str(), buf.size(), &written, nullptr);//Запись строки в канал
//Переход к закрытию канала if (buf == "quit")
break;
char msg_buffer[MAX_BUFFER_SIZE];
ReadFile(pipe, msg_buffer, MAX_BUFFER_SIZE, &read, nullptr);//Считывание ответа сервера msg_buffer[read] = 0;
std::cout << msg_buffer << '\n';//Отображение ответа сервера
}
CloseHandle(pipe);//Закрытие канала return EXIT_SUCCESS;
}
6