diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/_client_.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/_client_.h new file mode 100644 index 0000000..98abf83 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/_client_.h @@ -0,0 +1,53 @@ +#ifndef _CLIENT_H +#define _CLIENT_H + +#include +#include +#include +#include +#include + +#include "options/clientoption.h" +#include "options/consumeoption.h" +#include "options/exchangeoption.h" + +using namespace std; + +#define HEARBEATS "@@__Control__@@" + +class Sender; +class Receiver; + +class Client : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + + QList *_senders; + QList *_receivers; + +public: + Client(); + Client(ClientOption option); + virtual ~Client(); + + Client& operator=(Client client); + QString getVersion() const; + + ClientOption getClientOption() const { return clientOption; } + + Sender *createSender(ExchangeOption& option); + void removeSender(Sender *sender); + + Receiver *createReceiver(ConsumeOption& option); + void removeReceiver(Receiver *receiver); + +signals: + void onStop(); +}; + +Q_DECLARE_METATYPE(string) + +#endif // _CLIENT_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/client.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/client.cpp new file mode 100644 index 0000000..3f905ba --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/client.cpp @@ -0,0 +1,124 @@ + +#include "_client_.h" +#include "sender.h" +#include "receiver.h" + + +// Конструктор для связи с локальным RabbitMQ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Client::Client() : QObject(nullptr) +{ + { + static const int idMsg = qRegisterMetaType(); + static const int idMsgPtr = qRegisterMetaType(); + static const int idString = qRegisterMetaType(); + + Q_UNUSED(idMsg) + Q_UNUSED(idMsgPtr) + Q_UNUSED(idString) + } + + _senders = new QList(); + _senders->clear(); + + _receivers = new QList(); + _receivers->clear(); + +} + + + +Client::Client(ClientOption option) : Client() +{ + clientOption = option; +} + + +Client::~Client() +{ + for (auto &sender : *_senders) + delete sender; + delete _senders; + + for (auto &receiver : *_receivers) + delete receiver; + delete _receivers; +} + + +Client& Client::operator=(Client client) +{ + if (this != &client) { + this->clientOption = client.clientOption; + + this->_senders = new QList(); + this->_senders->clear(); + + this->_receivers = new QList(); + this->_receivers->clear(); + } + + return *this; +} + + +int majorVersion = 1; +int minorVersion = 1; +int releaseVersion = 1; + +QString Client::getVersion() const +{ + return QString::number(majorVersion) + + "." + QString::number(minorVersion) + + "." + QString::number(releaseVersion); +} + + + +// Создание публикатора (издателя) сообщений +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sender *Client::createSender(ExchangeOption& option) +{ + Sender *sender = new Sender(clientOption, option); + + connect(this, &Client::onStop, sender, &Sender::slotStop); + connect(this, &Client::onStop, sender, &Sender::deleteLater); + + _senders->append(sender); + + return sender; +} + +void Client::removeSender(Sender *sender) +{ + if ( !_senders->contains(sender)) + return; + sender->slotStop(); + _senders->removeOne(sender); +} + + +// Создание потребителя сообщений +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Receiver *Client::createReceiver(ConsumeOption& option) +{ + Receiver *receiver = new Receiver(clientOption, option); + + connect(this, &Client::onStop, receiver, &Receiver::slotStop); + connect(this, &Client::onStop, receiver, &Receiver::deleteLater); + + _receivers->append(receiver); + + return receiver; +} + +void Client::removeReceiver(Receiver *receiver) +{ + if ( !_receivers->contains(receiver)) + return; + receiver->slotStop(); + _receivers->removeOne(receiver); +} + + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/client_cpp.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/client_cpp.h new file mode 100644 index 0000000..04c35dc --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/client_cpp.h @@ -0,0 +1,22 @@ +#ifndef CLIENT_CPP_H +#define CLIENT_CPP_H + +#include "clientrbcpp_global.h" +#include "clientrbcpp.h" +#include "_client_.h" +#include "producemessage.h" +#include "properties.h" +#include "cworker.h" +#include "headers.h" +#include "pworker.h" +#include "receiver.h" +#include "sender.h" +#include "validator.h" +#include "vworker.h" + +#include "options/clientoption.h" +#include "options/consumeoption.h" +#include "options/exchangeoption.h" +#include "options/queueoption.h" + +#endif // CLIENT_CPP_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp.cpp new file mode 100644 index 0000000..06a2a25 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp.cpp @@ -0,0 +1,6 @@ +#include "clientrbcpp.h" + + +ClientRBcpp::ClientRBcpp() +{ +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp.h new file mode 100644 index 0000000..eba40e2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp.h @@ -0,0 +1,13 @@ +#ifndef CLIENTRBCPP_H +#define CLIENTRBCPP_H + +#include "clientrbcpp_global.h" + +class CLIENTRBCPPSHARED_EXPORT ClientRBcpp +{ + +public: + ClientRBcpp(); +}; + +#endif // CLIENTRBCPP_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp_global.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp_global.h new file mode 100644 index 0000000..6fa749e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/clientrbcpp_global.h @@ -0,0 +1,12 @@ +#ifndef CLIENTRBCPP_GLOBAL_H +#define CLIENTRBCPP_GLOBAL_H + +#include + +#if defined(CLIENTRBCPP_LIBRARY) +# define CLIENTRBCPPSHARED_EXPORT Q_DECL_EXPORT +#else +# define CLIENTRBCPPSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // CLIENTRBCPP_GLOBAL_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/cworker.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/cworker.cpp new file mode 100644 index 0000000..b27e98c --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/cworker.cpp @@ -0,0 +1,276 @@ +#include + +#include "client_cpp.h" + +#include + +CWorker::CWorker(ClientOption& clientOption, ConsumeOption& consumeOption) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->consumeOption = consumeOption; + this->consumeOption.receivingExchange = consumeOption.receivingExchange; + + connection = nullptr; + channel = nullptr; +} + + + +CWorker::~CWorker() +{ + +} + + +// Здесь реализуется основная деятельность потока +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void CWorker::doWork() +{ + + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + +// AMQP::TcpConnection connection(&handler, AMQP::Address(address)); +// AMQP::TcpChannel channel(&connection); + + connection = new AMQP::TcpConnection(&handler, AMQP::Address(address)); + channel = new AMQP::TcpChannel(connection); + + channel->setQos(1); + + channel->onError([&](const char *message) { + Q_UNUSED(message) + emit onErrorConsume("Channel error!!!"); + }); + + QTimer tm; + tm.stop(); + tm.setInterval(30000); + connect(&tm, &QTimer::timeout, this, [&]() { + tm.stop(); + connection->heartbeat(); + tm.start(); + }); + tm.start(); + + // Обработка принятого сообщения + auto messageCb = [&](const AMQP::Message& message, uint64_t deliveryTag, bool redelivered) + { + Q_UNUSED(redelivered) + + // Формируем принятое сообщениев формате ReceivedMessage + ProduceMessage *rMsg = new ProduceMessage; + rMsg->setBodyMsg(message.body(), message.bodySize()); + + // Формируем таблицу Properties свойств сообщения + Properties p; + if (message.hasContentType()) + p.setContentType(QString::fromStdString(message.contentType())); + if (message.hasContentEncoding()) + p.setContentEncoding(QString::fromStdString(message.contentEncoding())); + if (message.hasMessageID()) + p.setMessageID(QString::fromStdString(message.messageID())); + if (message.hasCorrelationID()) + p.setCorrelationID(QString::fromStdString(message.correlationID())); + if (message.timestamp()) + p.setTimestamp(message.timestamp()); + if (message.hasExpiration()) + p.setExpiration(QString::fromStdString(message.expiration())); + if (message.hasDeliveryMode()) + p.setDeliveryMode(message.deliveryMode()); + if (message.hasAppID()) + p.setAppID(QString::fromStdString(message.appID())); + if (message.hasUserID()) + p.setUserID(QString::fromStdString(message.userID())); + if (message.hasTypeName()) + p.setTypeName(QString::fromStdString(message.typeName())); + if (message.hasReplyTo()) + p.setReplyTo(QString::fromStdString(message.replyTo())); + if (message.hasPriority()) + p.setPriority(message.priority()); + + rMsg->setProperties(p); + + // Работа со свойствами Headers + Headers h; + AMQP::Table table = message.headers(); + vector keys = table.keys(); + + string name; + for(uint i = 0; i < keys.size(); i++) { + name = keys[i]; + if (table.get(name).isInteger()) { + int value = table.get(name); + h.set(QString::fromStdString(name), value); + } + else if (table.get(name).isString()) { + QString str = QString::fromStdString(table.get(name)); + h.set(QString::fromStdString(name), str); + } + else if (table.get(name).isBoolean()) { + AMQP::Field &b = const_cast(table.get(name)); + AMQP::BooleanSet &b1 = dynamic_cast(b); + bool value = b1.get(0); + h.set(QString::fromStdString(name), value); + } + } + + rMsg->setHeaders(h); + emit onResultReady(rMsg, deliveryTag); + + channel->ack(deliveryTag); + }; + + // объявление точки обмена + + if (!consumeOption.receivingExchange.name.isEmpty()) { + string exchange = consumeOption.receivingExchange.name.toStdString(); + string type = consumeOption.receivingExchange.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + AMQP::ExchangeType typeEx; + if (type == "" || type == "direct") + typeEx = AMQP::direct; + else if (type == "topic") + typeEx = AMQP::topic; + else if (type == "headers") + typeEx = AMQP::headers; + else + typeEx = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + int flagsExchange = 0; + if (consumeOption.receivingExchange.durable) + flagsExchange |= AMQP::durable; + if (consumeOption.receivingExchange.auto_delete) + flagsExchange |= AMQP::autodelete; + if (consumeOption.receivingExchange.internal) + flagsExchange |= AMQP::internal; + + AMQP::Table tableExch; + QString alt_e_name = "alternate-exchange"; + QString alt_e_value = ""; + if (consumeOption.receivingExchange.arguments.contains(alt_e_name)) { + alt_e_value = consumeOption.receivingExchange.arguments[alt_e_name].value(); + tableExch.set(alt_e_name.toStdString(), alt_e_value.toStdString()); + } + + // Для предопределенных точек обмена их обьявление не производим + if ( exchange != "" && exchange != "amq.fanout" && exchange != "amq.direct" && + exchange != "amq.topic" && exchange != "amq.headers") { + channel->declareExchange(exchange, typeEx, flagsExchange, tableExch) + .onError([&](const char *description) { + qDebug() << description; + }); + } + + QMultiMap::iterator it = consumeOption.bindingArgs.begin(); + for(; it != consumeOption.bindingArgs.end(); ++it) { + channel->bindExchange(it.key().toStdString(), exchange, it.value().toStdString()).onError([&](const char *description) { + qDebug() << description; + }); + } + } + + // объявление очереди + + QueueOption option = consumeOption.queueOption; + + string exchange = consumeOption.exchange.toStdString(); + string queue = option.name.toStdString(); + + // Подготовка флагов для объявления очереди + int flagsQueue = 0; + if (option.durable) + flagsQueue |= AMQP::durable; + if (option.auto_delete) + flagsQueue |= AMQP::autodelete; + if (option.exclusive) + flagsQueue |= AMQP::exclusive; + + channel->declareQueue(queue, flagsQueue) + .onSuccess( [&](const string &name, uint32_t messageCount, uint32_t consumerCount) { + Q_UNUSED(messageCount) + Q_UNUSED(consumerCount) + queue = name; + if (exchange != "") + for (QString rk : consumeOption.bindingKeys) { + channel->bindQueue(exchange, queue, rk.toStdString()) + .onError( [&](const char *description) { + qDebug() << description; + }); + } + }); + + // Подготовка флагов потребления сообщений + int flagsConsume = 0; + if (consumeOption.nolocal) + flagsConsume |= AMQP::nolocal; + if (consumeOption.noack) + flagsConsume |= AMQP::noack; + if (consumeOption.exclusive) + flagsConsume |= AMQP::exclusive; + + + channel->consume(queue, flagsConsume).onReceived(messageCb) + .onSuccess( [&](const string& tag) { + nextTag = tag; + }) + .onError( [&](const char *description) { + emit onErrorConsume(description); + markStop = true; // Останов потока + }); + + //Цикл обработки событий + while(!markStop) { + + event_base_loop(evbase, EVLOOP_ONCE); + QCoreApplication::processEvents(); + } + + // Закроем канал и соединение + channel->close(); + connection->close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); + + delete channel; + delete connection; + + emit onWorkFinished(); // Посылаем сигнал о завершении работы +} + + +void CWorker::slotStop() +{ + markStop = true; + channel->cancel(nextTag); // Отменить потребление + + channel->close(); + connection->close(); + +} + +void CWorker::bind(QString exchange, QString key, bool ex) +{ + if (ex) channel->bindExchange(exchange.toStdString(), consumeOption.exchange.toStdString(), key.toStdString()); + else channel->bindQueue(exchange.toStdString(), consumeOption.queueOption.name.toStdString(), key.toStdString()); +} + +void CWorker::unbind(QString exchange, QString key, bool ex) +{ + if (ex) channel->unbindExchange(exchange.toStdString(), consumeOption.exchange.toStdString(), key.toStdString()); + else channel->unbindQueue(exchange.toStdString(), consumeOption.queueOption.name.toStdString(), key.toStdString()); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/cworker.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/cworker.h new file mode 100644 index 0000000..f367037 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/cworker.h @@ -0,0 +1,54 @@ +#ifndef CWORKER_H +#define CWORKER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + + +class CWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ConsumeOption consumeOption; + + bool markStop; + + AMQP::TcpConnection *connection; + AMQP::TcpChannel *channel; + + string nextTag; + +public: + CWorker(ClientOption& clientOption, ConsumeOption& consumeOption); + virtual ~CWorker(); + +public slots: + void doWork(); + void slotStop(); + void bind(QString exchange, QString key, bool ex); + void unbind(QString exchange, QString key, bool ex); + +signals: + void onResultReady(PtrProduceMessage msg, uint64_t deliveryTag); + void onErrorConsume(const char *description); + void onWorkFinished(); +}; + + +#endif // CWORKER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/headers.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/headers.cpp new file mode 100644 index 0000000..9f56cb9 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/headers.cpp @@ -0,0 +1,40 @@ +#include "headers.h" + +Headers::Headers() +{ + _headers.clear(); +} + +Headers::Headers(const Headers& h) +{ + this->_headers = h._headers; +} + +void Headers::operator=(const Headers& h) +{ + this->_headers = h._headers; +} + + +QMap Headers::getHeaders() const { return _headers; } + +QList Headers::keys() const { return _headers.keys(); } +QList Headers::values() const { return _headers.values(); } + +int Headers::size() const { return _headers.size(); } + +bool Headers::contains(const QString name) const { return _headers.contains(name); } + + +void Headers::set(const QString name, bool value) { _headers.insert(name, value); } +void Headers::set(const QString name, int value) { _headers.insert(name, value); } +void Headers::set(const QString name, QString str) { _headers.insert(name, str); } + +bool Headers::isBool(const QString name) const { return _headers[name].type() == QVariant::Bool; } +bool Headers::isInteger(const QString name) const { return _headers[name].type() == QVariant::Int; } +bool Headers::isString(const QString name) const { return _headers[name].type() == QVariant::String; } + +bool Headers::getBool(const QString name) const { return _headers[name].value(); } +int Headers::getInteger(const QString name) const { return _headers[name].value(); } +QString Headers::getString(const QString name) const { return _headers[name].value(); } + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/headers.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/headers.h new file mode 100644 index 0000000..336a88a --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/headers.h @@ -0,0 +1,45 @@ +#ifndef HEADERS_H +#define HEADERS_H + +#include +#include +#include +#include + +class Headers +{ +private: + QMap _headers; + +public: + Headers(); + Headers(const Headers& h); // Конструктор копирования + ~Headers() {} + + void operator=(const Headers& h); + + QMap getHeaders() const; + + QList keys() const; + QList values() const; + + int size() const; + + bool contains(const QString name) const; + + void set(const QString name, bool value); + void set(const QString name, int value); + void set(const QString name, QString str); + + bool isBool(const QString name) const; + bool isInteger(const QString name) const; + bool isString(const QString name) const; + + bool getBool(const QString name) const; + int getInteger(const QString name) const; + QString getString(const QString name) const; + +}; + + +#endif // HEADERS_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/clientoption.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/clientoption.h new file mode 100644 index 0000000..5bccc3e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/clientoption.h @@ -0,0 +1,45 @@ +#ifndef CLIENTOPTION_H +#define CLIENTOPTION_H + +#include + +// Значения по умолчанию +const QString DEFAULT_CPP_HOST = "localhost"; +const int DEFAULT_CPP_PORT = 5672; +const QString DEFAULT_CPP_USER = "guest"; +const QString DEFAULT_CPP_PASSWORD = "guest"; +const QString DEFAULT_CPP_VHOST = "/"; + + +struct ClientOption { + QString host; + int port; + QString user; + QString password; + QString vhost; + + ClientOption() { + host = DEFAULT_CPP_HOST; + port = DEFAULT_CPP_PORT; + user = DEFAULT_CPP_USER; + password = DEFAULT_CPP_PASSWORD; + vhost = DEFAULT_CPP_VHOST; + } + + ~ClientOption() {} + + ClientOption(const ClientOption& src) = default; // Конструктор копирования + ClientOption(ClientOption&& src) = default; // Конструктор перемещения + + ClientOption& operator=(const ClientOption rhs) // Оператор присваивания + { + host = rhs.host; + port = rhs.port; + user = rhs.user; + password = rhs.password; + vhost = rhs.vhost; + return *this; + } +}; + +#endif // CLIENTOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/consumeoption.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/consumeoption.h new file mode 100644 index 0000000..8aef020 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/consumeoption.h @@ -0,0 +1,52 @@ +#ifndef CONSUMEOPTION_H +#define CONSUMEOPTION_H + +#include +#include +#include + +#include "queueoption.h" +#include "exchangeoption.h" + +struct ConsumeOption +{ + QString exchange; // Имя точки обмена для связывания + QStringList bindingKeys; // ключи связи точки с очередью + + bool nolocal; + bool noack; + bool exclusive; + + QueueOption queueOption; // Параметры очереди + ExchangeOption receivingExchange; // Параметры новой принимающей очереди (по умолчанию новой не создаётся) + QMultiMap bindingArgs; // список связей для точки обмена (если создаётся новая точка) + + ConsumeOption() { + exchange = ""; + receivingExchange.name = ""; + + nolocal = false; + noack = false; + exclusive = false; + } + + ~ConsumeOption() {} + + ConsumeOption(const ConsumeOption& src) = default; // Конструктор копирования + ConsumeOption(ConsumeOption&& src) = default; // Конструктор перемещения + + ConsumeOption& operator=(const ConsumeOption rhs) // Оператор присваивания + { + exchange = rhs.exchange; + bindingKeys = rhs.bindingKeys; + nolocal = rhs.nolocal; + noack = rhs.noack; + exclusive = rhs.exclusive; + queueOption = rhs.queueOption; + bindingArgs = rhs.bindingArgs; + return *this; + } + +}; + +#endif // CONSUMEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/exchangeoption.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/exchangeoption.h new file mode 100644 index 0000000..2c39cd2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/exchangeoption.h @@ -0,0 +1,55 @@ +#ifndef EXCHANGEOPTION_H +#define EXCHANGEOPTION_H + +#include +#include +#include + +struct ExchangeOption +{ + QString name; // уникальное имя точки обмена + QString type; // тип точки обмена (direct, topic, + // fanout или headers) + bool auto_delete; // автоматически удаляемая точка обмена + bool durable; // долгоживущая точка обмена + bool passive; // требуется информация о точке обмена + bool internal; // нельзя вести публикацию из приложения + + QVariantMap arguments; // необязательные аргументы + QMap bindingArgs; // список связей для точки обмена + + bool ifunused; // можно удалять, только если точка обмена + // не используется (не имеет потребителей) + ExchangeOption() { + name = ""; + type = ""; + auto_delete = false; + durable = false; + passive = false; + internal = false; + arguments.clear(); + + ifunused = false; + } + + ~ExchangeOption() {} + + ExchangeOption(const ExchangeOption& src) = default; // Конструктор копирования + ExchangeOption(ExchangeOption&& src) = default; // Конструктор перемещения + + ExchangeOption& operator=(const ExchangeOption rhs) // Оператор присваивания + { + name = rhs.name; + type = rhs.type; + auto_delete = rhs.auto_delete; + durable = rhs.durable; + passive = rhs.passive; + internal = rhs.internal; + arguments = rhs.arguments; + + ifunused = rhs.ifunused; + return *this; + } +}; + +#endif // EXCHANGEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/queueoption.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/queueoption.h new file mode 100644 index 0000000..2ca941e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/options/queueoption.h @@ -0,0 +1,63 @@ +#ifndef QUEUEOPTION_H +#define QUEUEOPTION_H + +#include +#include +#include + +struct QueueOption +{ + QString name; // Уникальное имя очереди + + bool auto_delete; // Автоматически удаляемая очередь + bool durable; // Долгоживущая очередь + bool passive; // Требуется информация об очереди + bool exclusive; // Исключительная очередь + + QVariantMap arguments; // Необязательные аргументы очереди + + bool ifunused; // Удалять, только если не используется + bool ifempty; // Удалять, только если очередь пуста + + int messageCount; // Число сообщений в очереди + int consumerCount; // Число потребителей очереди + + QueueOption() { + name = ""; + + auto_delete = false; + durable = false; + passive = false; + exclusive = false; + + arguments.clear(); + + ifunused = false; + ifempty = false; + + messageCount = 0; + consumerCount = 0; + } + + ~QueueOption() {} + + QueueOption(const QueueOption& src) = default; // Конструктор копирования + QueueOption(QueueOption&& src) = default; // Конструктор перемещения + + QueueOption& operator=(const QueueOption rhs) // Оператор присваивания + { + name = rhs.name; + auto_delete = rhs.auto_delete; + passive = rhs.passive; + exclusive = rhs.exclusive; + arguments = rhs.arguments; + + ifunused = rhs.ifunused; + ifempty = rhs.ifempty; + messageCount = rhs.messageCount; + consumerCount = rhs.consumerCount; + return *this; + } +}; + +#endif // QUEUEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/producemessage.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/producemessage.cpp new file mode 100644 index 0000000..95c28fc --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/producemessage.cpp @@ -0,0 +1,37 @@ +#include "producemessage.h" + +// Конструктор по умолчанию +ProduceMessage::ProduceMessage() +{ + _body.clear(); +} + +// Конструктор копирования +ProduceMessage::ProduceMessage(const ProduceMessage& msg) +{ + this->_body = msg._body; + this->_headers = msg._headers; + this->_properties = msg._properties; +} + +ProduceMessage& ProduceMessage::operator=(const ProduceMessage& msg) +{ + if (this != &msg) { + this->_body = msg._body; + this->_headers = msg._headers; + this->_properties = msg._properties; + } + return *this; +} + +QByteArray ProduceMessage::getBodyMsg() const { return _body; } +void ProduceMessage::setBodyMsg(const QByteArray &ba) { _body = ba; } +void ProduceMessage::setBodyMsg(const char *body, int size) { _body = QByteArray(body, size); } + +Headers ProduceMessage::getHeaders() const { return _headers; } +void ProduceMessage::setHeaders(const Headers &headers) { _headers = headers; } + +Properties ProduceMessage::getProperties() const { return _properties; } +void ProduceMessage::setProperties(const Properties &properties) { _properties = properties; } + + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/producemessage.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/producemessage.h new file mode 100644 index 0000000..bfb9413 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/producemessage.h @@ -0,0 +1,43 @@ +#ifndef PRODUCEMESSAGE_H +#define PRODUCEMESSAGE_H + +#include + +#include "properties.h" +#include "headers.h" + + +class ProduceMessage +{ +public: + ProduceMessage(); + ProduceMessage(const ProduceMessage& msg); + ~ProduceMessage() {} + + ProduceMessage& operator=(const ProduceMessage& msg); + + QByteArray getBodyMsg() const; + void setBodyMsg(const QByteArray &ba); + void setBodyMsg(const char *body, int size); + + Headers getHeaders() const; + void setHeaders(const Headers &headers); + + Properties getProperties() const; + void setProperties(const Properties &properties); + +private: + QByteArray _body; + +protected: + Properties _properties; + Headers _headers; +}; + + +using PtrProduceMessage = ProduceMessage*; + +Q_DECLARE_METATYPE(ProduceMessage) +Q_DECLARE_METATYPE(PtrProduceMessage) + +#endif // PRODUCEMESSAGE_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/properties.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/properties.cpp new file mode 100644 index 0000000..534eaa6 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/properties.cpp @@ -0,0 +1,63 @@ +#include "properties.h" + +Properties::Properties() +{ + _properties.clear(); + setDeliveryMode(1); // не оставлять сообщения +} + +// Конструктор копирования +Properties::Properties(const Properties& p) +{ + this->_properties = p._properties; +} + + +void Properties::operator=(const Properties& p) +{ + this->_properties = p._properties; +} + + +const QMap &Properties::getProperties() { return _properties; } + +bool Properties::contains(const QString name) const { return _properties.contains(name); } + +bool Properties::isContentType() const { return _properties.contains("content-type"); } +bool Properties::isContentEncoding() const { return _properties.contains("content-encoding"); } +bool Properties::isMessageID() const { return _properties.contains("message-id"); } +bool Properties::isCorrelationID() const { return _properties.contains("correlation-id"); } +bool Properties::isTimestamp() const { return _properties.contains("timestamp"); } +bool Properties::isExpiration() const { return _properties.contains("expiration"); } +bool Properties::isDeliveryMode() const { return _properties.contains("delivery-mode"); } +bool Properties::isAppID() const { return _properties.contains("app-id"); } +bool Properties::isUserID() const { return _properties.contains("user-id"); } +bool Properties::isTypeName() const { return _properties.contains("type"); } +bool Properties::isReplyTo() const { return _properties.contains("reply-to"); } +bool Properties::isPriority() const { return _properties.contains("priority"); } + +void Properties::setContentType(const QString &str) { _properties.insert("content-type", str); } +void Properties::setContentEncoding(const QString &str) { _properties.insert("content-encoding", str); } +void Properties::setMessageID(const QString &str) { _properties.insert("message-id", str); } +void Properties::setCorrelationID(const QString &str) { _properties.insert("correlation-id", str); } +void Properties::setTimestamp(const quint64 val) { _properties.insert("timestamp", val); } +void Properties::setExpiration(const QString &str) { _properties.insert("expiration", str); } +void Properties::setDeliveryMode(const quint8 val) { _properties.insert("delivery-mode", val); } +void Properties::setAppID(const QString &str) { _properties.insert("app-id", str); } +void Properties::setUserID(const QString &str) { _properties.insert("user-id", str); } +void Properties::setTypeName(const QString &str) { _properties.insert("type", str); } +void Properties::setReplyTo(const QString &str) { _properties.insert("reply-to", str); } +void Properties::setPriority(const quint8 val) { _properties.insert("priority", val); } + +QString Properties::getContentType() const { return _properties["content-type"].value(); } +QString Properties::getContentEncoding() const { return _properties["content-encoding"].value(); } +QString Properties::getMessageID() const { return _properties["message-id"].value(); } +QString Properties::getCorrelationID() const { return _properties["correlation-id"].value(); } +quint64 Properties::getTimestamp() const { return _properties["timestamp"].value(); } +QString Properties::getExpiration() const { return _properties["expiration"].value(); } +quint8 Properties::getDeliveryMode() const { return _properties["delivery-mode"].value(); } +QString Properties::getAppID() const { return _properties["app-id"].value(); } +QString Properties::getUserID() const { return _properties["user-id"].value(); } +QString Properties::getTypeName() const { return _properties["type"].value(); } +QString Properties::getReplyTo() const { return _properties["reply-to"].value(); } +quint8 Properties::getPriority() const { return _properties["priority"].value(); } diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/properties.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/properties.h new file mode 100644 index 0000000..d8a19f4 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/properties.h @@ -0,0 +1,73 @@ +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include + +#include +#include +#include + +class Properties +{ +private: + QMap _properties; + +public: + Properties(); + Properties(const Properties& p); + ~Properties() {} + + void operator=(const Properties& p); + + int size() {return _properties.size(); } + + const QMap &getProperties(); + + bool contains(const QString name) const; + + bool isContentType() const; + bool isContentEncoding() const; + bool isMessageID() const; + bool isCorrelationID() const; + bool isTimestamp() const; + bool isExpiration() const; + bool isDeliveryMode() const; + bool isAppID() const; + bool isUserID() const; + bool isTypeName() const; + bool isReplyTo() const; + bool isPriority() const; + + void setContentType(const QString &str); + void setContentEncoding(const QString &str); + void setMessageID(const QString &str); + void setCorrelationID(const QString &str); + void setTimestamp(const quint64 val); + void setExpiration(const QString &str); + void setDeliveryMode(const quint8 val); + void setAppID(const QString &str); + void setUserID(const QString &str); + void setTypeName(const QString &str); + void setReplyTo(const QString &str); + void setPriority(const quint8 val); + + QString getContentType() const; + QString getContentEncoding() const; + QString getMessageID() const; + QString getCorrelationID() const; + quint64 getTimestamp() const; + QString getExpiration() const; + quint8 getDeliveryMode() const; + QString getAppID() const; + QString getUserID() const; + QString getTypeName() const; + QString getReplyTo() const; + quint8 getPriority() const; +}; + +#endif // PROPERTIES_H + + + + + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/pworker.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/pworker.cpp new file mode 100644 index 0000000..6be4305 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/pworker.cpp @@ -0,0 +1,330 @@ +/************************************************************************** + * PWorker - Publish Worker, - рабочий поток публикации сообщений * + * редакция от 08.06.2022 * + * Принадлежность: библиотека clientRBcpp * + **************************************************************************/ + +#include + +#include "_client_.h" +#include "sender.h" +#include "pworker.h" +#include + + +PWorker::PWorker(ClientOption& clientOption, ExchangeOption& exchangeOption) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->exchangeOption = exchangeOption; + + qu.clear(); + +// static const int idE2E = qRegisterMetaType(); +// Q_UNUSED(idE2E) + +} + + +PWorker::~PWorker() +{ + // Освободим очередь сообщений + mutex.lock(); + while (! qu.isEmpty()) { + PublishPacket *packet = qu.dequeue(); + AMQP::Envelope *envelope = packet->envelope; + delete envelope->body(); + delete envelope; + delete packet; + } + mutex.unlock(); +} + + +// Здесь реализуется основная деятельность потока +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void PWorker::doWork() +{ + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + // Обрабатываем аргументы на предмет альтернативной точки обмена + AMQP::Table table; + QString alt_e_name = "alternate-exchange"; + QString alt_e_value = ""; + if (exchangeOption.arguments.contains(alt_e_name)) { + alt_e_value = exchangeOption.arguments[alt_e_name].value(); + table.set(alt_e_name.toStdString(), alt_e_value.toStdString()); + } + + string alt_exchange = alt_e_value.toStdString(); // Имя альтернативной точки обмена + AMQP::ExchangeType typeAltEx = AMQP::fanout; // Тип альтернативной точки обмена - всегда fanout + int flagsAltEx = (AMQP::durable | AMQP::internal); // Точка долгоживущая и внутренняя + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + AMQP::TcpConnection connection(&handler, AMQP::Address(address)); + AMQP::TcpChannel channel(&connection); + + channel.setQos(1); + + channel.confirmSelect() + .onAck([&](uint64_t deliveryTag, bool multiple) { + emit onReceivedAckNack(deliveryTag, true, multiple); + }) + .onNack([&](uint64_t deliveryTag, bool multiple, bool requeue) { + Q_UNUSED(requeue) + emit onReceivedAckNack(deliveryTag, false, multiple); + }); + + // Объявляем альтернативную точку обмена + //-------------------------------------- + if (alt_e_value != "") { + channel.declareExchange(alt_exchange, typeAltEx, flagsAltEx) + .onError( [&](const char *message) { + string msg(message); + emit onError(msg); + }); + } + + + // Обработка основной точки обмена + //---------------------------------- + string exchange = exchangeOption.name.toStdString(); + string type = exchangeOption.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + AMQP::ExchangeType typeEx; + if (type == "" || type == "direct") + typeEx = AMQP::direct; + else if (type == "topic") + typeEx = AMQP::topic; + else if (type == "headers") + typeEx = AMQP::headers; + else + typeEx = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + int flagsExchange = 0; + if (exchangeOption.durable) + flagsExchange |= AMQP::durable; + if (exchangeOption.auto_delete) + flagsExchange |= AMQP::autodelete; + if (exchangeOption.internal) + flagsExchange |= AMQP::internal; + + // Для предопределенных точек обмена их обьявление не производим + if ( exchange != "" && exchange != "amq.fanout" && exchange != "amq.direct" && + exchange != "amq.topic" && exchange != "amq.headers") { + channel.declareExchange(exchange, typeEx, flagsExchange, table) + .onError( [&](const char *message) { + string msg(message); + emit onError(msg); + }); + } + + // обработка mandatory + + auto messageCb = [&](const AMQP::Message& message, int16_t code, const std::string &description) + { + Q_UNUSED(code) + Q_UNUSED(description) + + // Формируем принятое сообщениев формате ReceivedMessage + ProduceMessage *rMsg = new ProduceMessage; + rMsg->setBodyMsg(message.body(), message.bodySize()); + + // Формируем таблицу Properties свойств сообщения + Properties p; + if (message.hasContentType()) + p.setContentType(QString::fromStdString(message.contentType())); + if (message.hasContentEncoding()) + p.setContentEncoding(QString::fromStdString(message.contentEncoding())); + if (message.hasMessageID()) + p.setMessageID(QString::fromStdString(message.messageID())); + if (message.hasCorrelationID()) + p.setCorrelationID(QString::fromStdString(message.correlationID())); + if (message.timestamp()) + p.setTimestamp(message.timestamp()); + if (message.hasExpiration()) + p.setExpiration(QString::fromStdString(message.expiration())); + if (message.hasDeliveryMode()) + p.setDeliveryMode(message.deliveryMode()); + if (message.hasAppID()) + p.setAppID(QString::fromStdString(message.appID())); + if (message.hasUserID()) + p.setUserID(QString::fromStdString(message.userID())); + if (message.hasTypeName()) + p.setTypeName(QString::fromStdString(message.typeName())); + if (message.hasReplyTo()) + p.setReplyTo(QString::fromStdString(message.replyTo())); + if (message.hasPriority()) + p.setPriority(message.priority()); + + rMsg->setProperties(p); + + // Работа со свойствами Headers + Headers h; + AMQP::Table table = message.headers(); + vector keys = table.keys(); + + string name; + for(uint i = 0; i < keys.size(); i++) { + name = keys[i]; + if (table.get(name).isInteger()) { + int value = table.get(name); + h.set(QString::fromStdString(name), value); + } + else if (table.get(name).isString()) { + QString str = QString::fromStdString(table.get(name)); + h.set(QString::fromStdString(name), str); + } + else if (table.get(name).isBoolean()) { + AMQP::Field &b = const_cast(table.get(name)); + AMQP::BooleanSet &b1 = dynamic_cast(b); + bool value = b1.get(0); + h.set(QString::fromStdString(name), value); + } + } + + rMsg->setHeaders(h); + emit onMessageBounced(rMsg); + }; + + channel.recall().onReceived(messageCb); + + // Цикл событий (event loop) + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + while (! markStop) { + + // Обрабатываем очередь сообщений на передачу + if (! qu.isEmpty()) { + + mutex.lock(); + PublishPacket *packet = qu.dequeue(); + mutex.unlock(); + + AMQP::Envelope *envelope = packet->envelope; + int publishFlags = packet->publishFlags; + string routingKey = packet->routingKey; + + if (envelope->hasAppID() && envelope->appID() == HEARBEATS) + connection.heartbeat(); + else + channel.publish(exchange, routingKey, *envelope, publishFlags); + + delete envelope->body(); + delete envelope; + delete packet; + } + + event_base_loop(evbase, EVLOOP_NONBLOCK); + QCoreApplication::processEvents(); + QThread::msleep(0); + } //while + + // Закроем канал и соединение + channel.close(); + connection.close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); + + emit onWorkFinished(); // Посылаем сигнал о завершении работы +} + + + +// Прием данных, предразование в формат передачи +// и постановка в очередь на выдачу +void PWorker::sending(ProduceMessage msg, QString routingKey, bool mandatory) +{ + mutex.lock(); + + uint64_t size = msg.getBodyMsg().size(); + char *body = new char[size]; + memcpy(body, msg.getBodyMsg().data(), size); + + AMQP::Envelope *env = new AMQP::Envelope(body, size); + + // Готовим сообщение для отправки + Properties p = msg.getProperties(); + if (p.contains("content-type")) + env->setContentType(p.getContentType().toStdString().c_str()); + if (p.contains("content-encoding")) + env->setContentEncoding(p.getContentEncoding().toStdString().c_str()); + if (p.contains("message-id")) + env->setMessageID(p.getMessageID().toStdString().c_str()); + if (p.contains("correlation-id")) + env->setCorrelationID(p.getCorrelationID().toStdString().c_str()); + if (p.contains("timestamp")) + env->setTimestamp(p.getTimestamp()); + if (p.contains("expiration")) + env->setExpiration(p.getExpiration().toStdString().c_str()); + if (p.contains("delivery-mode")) + env->setDeliveryMode(p.getDeliveryMode()); + if (p.contains("app-id")) + env->setAppID(p.getAppID().toStdString().c_str()); + if (p.contains("user-id")) + env->setUserID(p.getUserID().toStdString().c_str()); + if (p.contains("type")) + env->setTypeName(p.getTypeName().toStdString().c_str()); + if (p.contains("reply-to")) + env->setReplyTo(p.getReplyTo().toStdString().c_str()); + if (p.contains("priority")) + env->setPriority(p.getPriority()); + + AMQP::Table table; + + Headers p2 = msg.getHeaders(); + QList k = p2.keys(); + QList v = p2.values(); + for (int i=0; i < p2.size(); i++) { + QString name = k[i]; + QVariant val = v[i]; + if (val.type() == QVariant::Int) { + AMQP::Long numb = val.value(); + table.set(name.toStdString(), numb.value()); + } + else if (val.type() == QVariant::String) { + QString str = val.value(); + AMQP::ShortString s(str.toStdString()); + table.set(name.toStdString(), s.value()); + } + else if (val.type() == QVariant::Bool) { + bool numb = val.value(); + table.set(name.toStdString(), numb); + } + } + env->setHeaders(table); + + int flags = 0; // флаги - в формат AMQP + if (mandatory) + flags |= AMQP::mandatory; + + string routing = routingKey.toStdString(); + + // формируем пакет для постановки в очередь + PublishPacket *pp = new PublishPacket; + pp->envelope = env; + pp->publishFlags = flags; + pp->routingKey = routing; + + qu.enqueue(pp); + mutex.unlock(); + +} + + + +void PWorker::stop() +{ + markStop = true; // завершить цикл обработки сообщений +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/pworker.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/pworker.h new file mode 100644 index 0000000..9e3e25f --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/pworker.h @@ -0,0 +1,58 @@ +#ifndef PWORKER_H +#define PWORKER_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + + + +struct PublishPacket { + AMQP::Envelope *envelope; + string routingKey; + int publishFlags; +}; + + + +class PWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ExchangeOption exchangeOption; + + bool markStop; + QMutex mutex; + QQueue qu; + +public: + PWorker(ClientOption& clientOption, ExchangeOption& exchangeOption); + virtual ~PWorker(); + +public slots: + void doWork(); + void sending(ProduceMessage msg, QString routingKey, bool mandatory=false); + void stop(); + +signals: + void onMessageBounced(PtrProduceMessage msg); + void onError(string msg); + void onReceivedAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void onWorkFinished(); +}; + + +#endif // PWORKER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/receiver.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/receiver.cpp new file mode 100644 index 0000000..2553676 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/receiver.cpp @@ -0,0 +1,65 @@ +#include + +#include "client_cpp.h" + + + +Receiver::Receiver(ClientOption &clientOption, ConsumeOption &consumeOption) + : QObject(nullptr) +{ + this->clientOption = clientOption; + this->consumeOption = consumeOption; + this->consumeOption.receivingExchange = consumeOption.receivingExchange; + this->consumeOption.bindingArgs = consumeOption.bindingArgs; + + CWorker *worker = new CWorker(this->clientOption, this->consumeOption); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &CWorker::doWork); + connect(&workerThread, &QThread::finished, worker, &CWorker::slotStop); + + // Автоматическое удаление объектов PWorker и QThread по окончании работы + connect(worker, &CWorker::onWorkFinished, worker, &CWorker::deleteLater); + + // Посылаемые потоку сигналы + connect(this, &Receiver::onStop, worker, &CWorker::slotStop, Qt::DirectConnection); + connect(this, &Receiver::doBind, worker, &CWorker::bind, Qt::DirectConnection); + connect(this, &Receiver::doUnbind, worker, &CWorker::unbind, Qt::DirectConnection); + + // Сигналы, принимаемые от потока + connect(worker, &CWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &CWorker::onResultReady, this, &Receiver::slotMsgReady, Qt::QueuedConnection); + connect(worker, &CWorker::onErrorConsume, this, &Receiver::slotErrorMsg, Qt::QueuedConnection); + + workerThread.start(); + +} + + +Receiver::~Receiver() +{ + workerThread.quit(); + workerThread.wait(); +} + +// При получении от потока сигнала о приеме сообщения +// выпускаем сигнал дальше +void Receiver::slotMsgReady(PtrProduceMessage msg, uint64_t deliveryTag) +{ + ProduceMessage message = *msg; + delete msg; + emit onMessage(message, deliveryTag); +} + +// При получении сигнала об ошибке транслируем его +void Receiver::slotErrorMsg(const char *description) +{ + QString str(description); + emit onError(str); +} + + +void Receiver::slotStop() +{ + emit onStop(); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/receiver.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/receiver.h new file mode 100644 index 0000000..11ceb7b --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/receiver.h @@ -0,0 +1,47 @@ +#ifndef RECEIVER_H +#define RECEIVER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "_client_.h" +#include "cworker.h" + +using namespace std; + + +class Receiver : public QObject +{ + Q_OBJECT + +private: + bool stop; + + ClientOption clientOption; + ConsumeOption consumeOption; + QThread workerThread; + +public: + Receiver(ClientOption& clientOption, ConsumeOption& consumeOption); + virtual ~Receiver(); + +public slots: + void slotMsgReady(PtrProduceMessage msg, uint64_t deliveryTag); + void slotErrorMsg(const char *description); + void slotStop(); + +signals: + void onMessage(ProduceMessage msg, uint64_t deliveryTag); + void onError(QString description); + void onStop(); + void doBind(QString exchange, QString key, bool ex); + void doUnbind(QString exchange, QString key, bool ex); + +}; + +#endif // RECEIVER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/sender.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/sender.cpp new file mode 100644 index 0000000..51cbfd9 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/sender.cpp @@ -0,0 +1,106 @@ +#include + +#include "client_cpp.h" + + +Sender::Sender(ClientOption& clientOption, ExchangeOption& exchangeOption) + : QObject(nullptr) +{ + this->clientOption = clientOption; + this->exchangeOption = exchangeOption; + + PWorker *worker = new PWorker(this->clientOption, this->exchangeOption); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &PWorker::doWork); + + // Автоматическое удаление объектов PWorker и QThread по окончании работы + connect(worker, &PWorker::onWorkFinished, worker, &PWorker::deleteLater); + + // Посылаемые потоку сигналы + connect(this, &Sender::onSend, worker, &PWorker::sending, Qt::QueuedConnection); + connect(this, &Sender::onStop, worker, &PWorker::stop, Qt::DirectConnection); + + // Сигналы, принимаемые от потока + connect(worker, &PWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &PWorker::onReceivedAckNack, this, &Sender::slotAckNack, Qt::QueuedConnection); + connect(worker, &PWorker::onError, this, &Sender::slotError, Qt::QueuedConnection); + connect(worker, &PWorker::onMessageBounced, this, &Sender::slotMsgBounced, Qt::QueuedConnection); + + workerThread.start(); + + // Запуск таймера для механизма сердцебиения + tm.stop(); + tm.setInterval(30000); // 0,5 мин + connect(&tm, &QTimer::timeout, this, &Sender::onTimer); + tm.start(); +} + + +Sender::~Sender() +{ + tm.stop(); + + workerThread.quit(); + workerThread.wait(); +} + + +// Периодическое подключение по таймеру (1 мин) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void Sender::onTimer() +{ + tm.stop(); + + // Формируем и отправляем служебное сообщение + // на проверку соединения с экземпляром RabbitMQ + + string str = "@@"; // содержимое не играет роли + ProduceMessage msg; + msg.setBodyMsg(str.c_str(), str.size()); + + Properties p; + QString hearbeats(HEARBEATS); + p.setAppID(hearbeats); // маркер служебного сообщения сердцебиения + msg.setProperties(p); + + QString routingKey = ""; + emit onSend(msg, routingKey); // сообщение передаем в поток для передачи серверу + + tm.start(); +} + +void Sender::slotMsgBounced(PtrProduceMessage msg) +{ + ProduceMessage message = *msg; + delete msg; + emit onMsgBounced(message); +} + +// Передаем сообщение в поток для выдачи +void Sender::send(ProduceMessage msg, QString routingKey, bool mandatory) +{ + emit onSend(msg, routingKey, mandatory); +} + + +// Прием подтверждения от потока о выдаче (или невыдаче) сообщения +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------~~~~~~~~~~~~~~------------ +void Sender::slotAckNack(uint64_t deliveryTag, bool ack, bool multiple) +{ + emit onReceivedAckNack(deliveryTag, ack, multiple); +} + + +void Sender::slotError(string msg) +{ + QString message = QString::fromStdString(msg); + + emit onError(message); +} + + +void Sender::slotStop() +{ + emit onStop(); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/sender.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/sender.h new file mode 100644 index 0000000..4506bd3 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/sender.h @@ -0,0 +1,58 @@ +#ifndef SENDER_H +#define SENDER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "client_cpp.h" + +using namespace std; + +class Client; +class PWorker; + + +class Sender : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ExchangeOption exchangeOption; + QThread workerThread; + QTimer tm; + +public: + Sender(ClientOption& clientOption, ExchangeOption& exchangeOption); + virtual ~Sender(); + + + void send(ProduceMessage msg, QString routingKey, bool mandatory=false); + +private slots: + void onTimer(); + +public slots: + void slotMsgBounced(PtrProduceMessage msg); + void slotStop(); + void slotAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void slotError(string msg); + +signals: + void onMsgBounced(ProduceMessage msg); + void onReceivedAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void onSend(ProduceMessage msg, QString routingKey, bool mandatory=false); // Отправка сообщения потоку + void onError(QString &msg); + void onStop(); + +}; + + +#endif // SENDER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/validator.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/validator.cpp new file mode 100644 index 0000000..e80db05 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/validator.cpp @@ -0,0 +1,54 @@ +#include "validator.h" + +#include + +Validator::Validator(ClientOption& clientOption, QList& exchangeOptions) + : QObject(nullptr) +{ + // класс запускает поток с валидатором, который в течение 5 секунд должен проверить инфраструктуру. + // любая ошибка приведёт к завершению работы программы. если ошибок не было перехвачено, через 5 секунд процесс завершается. + + this->clientOption = clientOption; + this->exchangeOptions = exchangeOptions; + + VWorker *worker = new VWorker(this->clientOption, this->exchangeOptions); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &VWorker::doWork); + + // Автоматическое удаление объектов VWorker и QThread по окончании работы + connect(worker, &VWorker::onWorkFinished, worker, &VWorker::deleteLater); + + // Сигналы, принимаемые от потока + connect(worker, &VWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &VWorker::onError, this, &Validator::slotError, Qt::QueuedConnection); + + workerThread.start(); + + // Запуск таймера для механизма сердцебиения + tm.stop(); + tm.setInterval(5000); // 5 сек + connect(&tm, &QTimer::timeout, worker, &VWorker::slotStop); + connect(&tm, &QTimer::timeout, this, &Validator::onTimer); + tm.start(); +} + +Validator::~Validator() +{ + tm.stop(); + + workerThread.quit(); +} + +void Validator::onTimer() +{ + tm.stop(); + + workerThread.quit(); +} + +void Validator::slotError(string msg) +{ + QString message = QString::fromStdString(msg); + emit onError(message); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/validator.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/validator.h new file mode 100644 index 0000000..a717e74 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/validator.h @@ -0,0 +1,41 @@ +#ifndef VALIDATOR_H +#define VALIDATOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "client_cpp.h" + +class Validator : public QObject +{ + Q_OBJECT + +public: + Validator(ClientOption& clientOption, QList& exchangeOptions); + virtual ~Validator(); + +private: + ClientOption clientOption; + QList exchangeOptions; + QThread workerThread; + QTimer tm; + +private slots: + void onTimer(); + +public slots: + void slotError(string msg); + +signals: + void onError(QString &msg); + void onStop(); +}; + +#endif // VALIDATOR_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/vworker.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/vworker.cpp new file mode 100644 index 0000000..f4b31f6 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/vworker.cpp @@ -0,0 +1,123 @@ +#include + +#include "_client_.h" +#include "sender.h" +#include "vworker.h" + +#include + +VWorker::VWorker(ClientOption &clientOption, QList& exchangeOptions) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->exchangeOptions = exchangeOptions; +} + +VWorker::~VWorker() +{ + +} + +void VWorker::doWork() +{ + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + // Создаём подключение + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + AMQP::TcpConnection connection(&handler, AMQP::Address(address)); + AMQP::TcpChannel channel(&connection); + + channel.setQos(1); + + channel.confirmSelect() + .onError([&](const char *message) { + qDebug() << "validator - connecting error: " << message; + }); + + // Обрабатываем список точек обмена + + string exch; + string exType; + AMQP::ExchangeType typeExch; + int flagsExch = 0; + + QString ex_alt_e_name = "alternate-exchange"; + QString ex_alt_e_value = ""; + + for(ExchangeOption exOpt : exchangeOptions) { + AMQP::Table exTable; + exch = exOpt.name.toStdString(); + exType = exOpt.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + if (exType == "" || exType == "direct") + typeExch = AMQP::direct; + else if (exType == "topic") + typeExch = AMQP::topic; + else if (exType == "headers") + typeExch = AMQP::headers; + else + typeExch = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + if (exOpt.durable) + flagsExch |= AMQP::durable; + if (exOpt.auto_delete) + flagsExch |= AMQP::autodelete; + if (exOpt.internal) + flagsExch |= AMQP::internal; + + if (exOpt.arguments.contains(ex_alt_e_name)) { + ex_alt_e_value = exOpt.arguments[ex_alt_e_name].value(); + exTable.set(ex_alt_e_name.toStdString(), ex_alt_e_value.toStdString()); + } + + //Для предопределенных точек обмена их обьявление не производим + + if ( exch != "" && exch != "amq.fanout" && exch != "amq.direct" && + exch != "amq.topic" && exch != "amq.headers") { + channel.declareExchange(exch, typeExch, flagsExch, exTable) + .onError( [&](const char *message) { + qDebug() << "validator - declaring error: " << message; + emit onError(message); + }); + } + + QMap::iterator it = exOpt.bindingArgs.begin(); + for (; it != exOpt.bindingArgs.end(); ++it) { + channel.bindExchange(exch, it.key().toStdString(), it.value().toStdString()) + .onError( [&](const char *message) { + qDebug() << "validator - binding error: " << message; + emit onError(message); + }); + } + } + + while (! markStop) { + event_base_loop(evbase, EVLOOP_NONBLOCK); + QCoreApplication::processEvents(); + QThread::msleep(0); + } + + // Закроем канал и соединение + channel.close(); + connection.close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); +} + +void VWorker::slotStop() +{ + markStop = true; +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/vworker.h b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/vworker.h new file mode 100644 index 0000000..2019489 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/clientRBcpp/vworker.h @@ -0,0 +1,43 @@ +#ifndef VWORKER_H +#define VWORKER_H + +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + +class VWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + QList exchangeOptions; + + bool markStop; + QMutex mutex; + +public: + VWorker(ClientOption& clientOption, QList& exchangeOptions); + virtual ~VWorker(); + +public slots: + void doWork(); + void slotStop(); + +signals: + void onError(string msg); + void onWorkFinished(); +}; + + +#endif // VWORKER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro b/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro new file mode 100644 index 0000000..749c77b --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro @@ -0,0 +1,41 @@ +QT += core gui widgets + +LIBS += -L/usr/lib -lamqpcpp -L/usr/lib/x86_64-linux-gnu/ -levent -lpthread -ldl + +SOURCES += \ + clientRBcpp/client.cpp \ + clientRBcpp/clientrbcpp.cpp \ + clientRBcpp/cworker.cpp \ + clientRBcpp/headers.cpp \ + clientRBcpp/producemessage.cpp \ + clientRBcpp/properties.cpp \ + clientRBcpp/pworker.cpp \ + clientRBcpp/receiver.cpp \ + clientRBcpp/sender.cpp \ + clientRBcpp/validator.cpp \ + clientRBcpp/vworker.cpp \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + clientRBcpp/_client_.h \ + clientRBcpp/client_cpp.h \ + clientRBcpp/clientrbcpp.h \ + clientRBcpp/clientrbcpp_global.h \ + clientRBcpp/cworker.h \ + clientRBcpp/headers.h \ + clientRBcpp/options/clientoption.h \ + clientRBcpp/options/consumeoption.h \ + clientRBcpp/options/exchangeoption.h \ + clientRBcpp/options/queueoption.h \ + clientRBcpp/producemessage.h \ + clientRBcpp/properties.h \ + clientRBcpp/pworker.h \ + clientRBcpp/receiver.h \ + clientRBcpp/sender.h \ + clientRBcpp/validator.h \ + clientRBcpp/vworker.h \ + mainwindow.h + +FORMS += \ + mainwindow.ui diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro.user b/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro.user new file mode 100644 index 0000000..4323c28 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro.user @@ -0,0 +1,338 @@ + + + + + + EnvironmentId + {77607214-f3f8-45c8-bf65-1a310ea854a8} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + true + Builtin.Questionable + + true + Builtin.DefaultTidyAndClazy + 0 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + {fa463890-d98c-43fb-aee8-64b3da65bdfc} + 0 + 0 + 0 + + true + 0 + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Debug + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Debug + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Отладка + Qt4ProjectManager.Qt4BuildConfiguration + 2 + 2 + 2 + + + true + 2 + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Release + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Release + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Выпуск + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 2 + + + true + 0 + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Profile + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Profile + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Профилирование + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:/home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/consumer_fast/consumer_fast.pro + + false + + false + true + true + false + false + true + + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_fast-Desktop-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/main.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.cpp b/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.cpp new file mode 100644 index 0000000..695a467 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.cpp @@ -0,0 +1,31 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + ClientOption clOpt; + + ConsumeOption conOpt; + conOpt.exchange = "publisher"; + conOpt.bindingKeys << "all"; + conOpt.queueOption.name = "queue_fast"; + conOpt.queueOption.auto_delete = true; + + receiver = new Receiver(clOpt, conOpt); + QObject::connect(receiver, &Receiver::onMessage, this, [&](ProduceMessage msg, uint64_t consumeTag) { + Q_UNUSED(consumeTag) + + QString msg_body = "got " + QString::fromLocal8Bit(msg.getBodyMsg()); + ui->listWidget->addItem(msg_body); + }); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.h b/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.h new file mode 100644 index 0000000..28ea2fe --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.h @@ -0,0 +1,26 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "clientRBcpp/client_cpp.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + Receiver *receiver; + int counter; + +}; +#endif // MAINWINDOW_H diff --git a/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.ui b/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.ui new file mode 100644 index 0000000..7e2eac5 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_fast/mainwindow.ui @@ -0,0 +1,37 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + + + + + 0 + 0 + 800 + 30 + + + + + + + + diff --git a/alexandrov_dmitrii_lab_4/consumer_fast_service b/alexandrov_dmitrii_lab_4/consumer_fast_service new file mode 100755 index 0000000..5e6e53d Binary files /dev/null and b/alexandrov_dmitrii_lab_4/consumer_fast_service differ diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/_client_.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/_client_.h new file mode 100644 index 0000000..98abf83 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/_client_.h @@ -0,0 +1,53 @@ +#ifndef _CLIENT_H +#define _CLIENT_H + +#include +#include +#include +#include +#include + +#include "options/clientoption.h" +#include "options/consumeoption.h" +#include "options/exchangeoption.h" + +using namespace std; + +#define HEARBEATS "@@__Control__@@" + +class Sender; +class Receiver; + +class Client : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + + QList *_senders; + QList *_receivers; + +public: + Client(); + Client(ClientOption option); + virtual ~Client(); + + Client& operator=(Client client); + QString getVersion() const; + + ClientOption getClientOption() const { return clientOption; } + + Sender *createSender(ExchangeOption& option); + void removeSender(Sender *sender); + + Receiver *createReceiver(ConsumeOption& option); + void removeReceiver(Receiver *receiver); + +signals: + void onStop(); +}; + +Q_DECLARE_METATYPE(string) + +#endif // _CLIENT_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/client.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/client.cpp new file mode 100644 index 0000000..3f905ba --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/client.cpp @@ -0,0 +1,124 @@ + +#include "_client_.h" +#include "sender.h" +#include "receiver.h" + + +// Конструктор для связи с локальным RabbitMQ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Client::Client() : QObject(nullptr) +{ + { + static const int idMsg = qRegisterMetaType(); + static const int idMsgPtr = qRegisterMetaType(); + static const int idString = qRegisterMetaType(); + + Q_UNUSED(idMsg) + Q_UNUSED(idMsgPtr) + Q_UNUSED(idString) + } + + _senders = new QList(); + _senders->clear(); + + _receivers = new QList(); + _receivers->clear(); + +} + + + +Client::Client(ClientOption option) : Client() +{ + clientOption = option; +} + + +Client::~Client() +{ + for (auto &sender : *_senders) + delete sender; + delete _senders; + + for (auto &receiver : *_receivers) + delete receiver; + delete _receivers; +} + + +Client& Client::operator=(Client client) +{ + if (this != &client) { + this->clientOption = client.clientOption; + + this->_senders = new QList(); + this->_senders->clear(); + + this->_receivers = new QList(); + this->_receivers->clear(); + } + + return *this; +} + + +int majorVersion = 1; +int minorVersion = 1; +int releaseVersion = 1; + +QString Client::getVersion() const +{ + return QString::number(majorVersion) + + "." + QString::number(minorVersion) + + "." + QString::number(releaseVersion); +} + + + +// Создание публикатора (издателя) сообщений +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sender *Client::createSender(ExchangeOption& option) +{ + Sender *sender = new Sender(clientOption, option); + + connect(this, &Client::onStop, sender, &Sender::slotStop); + connect(this, &Client::onStop, sender, &Sender::deleteLater); + + _senders->append(sender); + + return sender; +} + +void Client::removeSender(Sender *sender) +{ + if ( !_senders->contains(sender)) + return; + sender->slotStop(); + _senders->removeOne(sender); +} + + +// Создание потребителя сообщений +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Receiver *Client::createReceiver(ConsumeOption& option) +{ + Receiver *receiver = new Receiver(clientOption, option); + + connect(this, &Client::onStop, receiver, &Receiver::slotStop); + connect(this, &Client::onStop, receiver, &Receiver::deleteLater); + + _receivers->append(receiver); + + return receiver; +} + +void Client::removeReceiver(Receiver *receiver) +{ + if ( !_receivers->contains(receiver)) + return; + receiver->slotStop(); + _receivers->removeOne(receiver); +} + + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/client_cpp.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/client_cpp.h new file mode 100644 index 0000000..04c35dc --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/client_cpp.h @@ -0,0 +1,22 @@ +#ifndef CLIENT_CPP_H +#define CLIENT_CPP_H + +#include "clientrbcpp_global.h" +#include "clientrbcpp.h" +#include "_client_.h" +#include "producemessage.h" +#include "properties.h" +#include "cworker.h" +#include "headers.h" +#include "pworker.h" +#include "receiver.h" +#include "sender.h" +#include "validator.h" +#include "vworker.h" + +#include "options/clientoption.h" +#include "options/consumeoption.h" +#include "options/exchangeoption.h" +#include "options/queueoption.h" + +#endif // CLIENT_CPP_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp.cpp new file mode 100644 index 0000000..06a2a25 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp.cpp @@ -0,0 +1,6 @@ +#include "clientrbcpp.h" + + +ClientRBcpp::ClientRBcpp() +{ +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp.h new file mode 100644 index 0000000..eba40e2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp.h @@ -0,0 +1,13 @@ +#ifndef CLIENTRBCPP_H +#define CLIENTRBCPP_H + +#include "clientrbcpp_global.h" + +class CLIENTRBCPPSHARED_EXPORT ClientRBcpp +{ + +public: + ClientRBcpp(); +}; + +#endif // CLIENTRBCPP_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp_global.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp_global.h new file mode 100644 index 0000000..6fa749e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/clientrbcpp_global.h @@ -0,0 +1,12 @@ +#ifndef CLIENTRBCPP_GLOBAL_H +#define CLIENTRBCPP_GLOBAL_H + +#include + +#if defined(CLIENTRBCPP_LIBRARY) +# define CLIENTRBCPPSHARED_EXPORT Q_DECL_EXPORT +#else +# define CLIENTRBCPPSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // CLIENTRBCPP_GLOBAL_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/cworker.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/cworker.cpp new file mode 100644 index 0000000..3e09b62 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/cworker.cpp @@ -0,0 +1,278 @@ +#include + +#include "client_cpp.h" + +#include + +CWorker::CWorker(ClientOption& clientOption, ConsumeOption& consumeOption) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->consumeOption = consumeOption; + this->consumeOption.receivingExchange = consumeOption.receivingExchange; + + connection = nullptr; + channel = nullptr; +} + + + +CWorker::~CWorker() +{ + +} + + +// Здесь реализуется основная деятельность потока +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void CWorker::doWork() +{ + + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + +// AMQP::TcpConnection connection(&handler, AMQP::Address(address)); +// AMQP::TcpChannel channel(&connection); + + connection = new AMQP::TcpConnection(&handler, AMQP::Address(address)); + channel = new AMQP::TcpChannel(connection); + + channel->setQos(1); + + channel->onError([&](const char *message) { + Q_UNUSED(message) + emit onErrorConsume("Channel error!!!"); + }); + + QTimer tm; + tm.stop(); + tm.setInterval(30000); + connect(&tm, &QTimer::timeout, this, [&]() { + tm.stop(); + connection->heartbeat(); + tm.start(); + }); + tm.start(); + + // Обработка принятого сообщения + auto messageCb = [&](const AMQP::Message& message, uint64_t deliveryTag, bool redelivered) + { + Q_UNUSED(redelivered) + + // Формируем принятое сообщениев формате ReceivedMessage + ProduceMessage *rMsg = new ProduceMessage; + rMsg->setBodyMsg(message.body(), message.bodySize()); + + // Формируем таблицу Properties свойств сообщения + Properties p; + if (message.hasContentType()) + p.setContentType(QString::fromStdString(message.contentType())); + if (message.hasContentEncoding()) + p.setContentEncoding(QString::fromStdString(message.contentEncoding())); + if (message.hasMessageID()) + p.setMessageID(QString::fromStdString(message.messageID())); + if (message.hasCorrelationID()) + p.setCorrelationID(QString::fromStdString(message.correlationID())); + if (message.timestamp()) + p.setTimestamp(message.timestamp()); + if (message.hasExpiration()) + p.setExpiration(QString::fromStdString(message.expiration())); + if (message.hasDeliveryMode()) + p.setDeliveryMode(message.deliveryMode()); + if (message.hasAppID()) + p.setAppID(QString::fromStdString(message.appID())); + if (message.hasUserID()) + p.setUserID(QString::fromStdString(message.userID())); + if (message.hasTypeName()) + p.setTypeName(QString::fromStdString(message.typeName())); + if (message.hasReplyTo()) + p.setReplyTo(QString::fromStdString(message.replyTo())); + if (message.hasPriority()) + p.setPriority(message.priority()); + + rMsg->setProperties(p); + + // Работа со свойствами Headers + Headers h; + AMQP::Table table = message.headers(); + vector keys = table.keys(); + + string name; + for(uint i = 0; i < keys.size(); i++) { + name = keys[i]; + if (table.get(name).isInteger()) { + int value = table.get(name); + h.set(QString::fromStdString(name), value); + } + else if (table.get(name).isString()) { + QString str = QString::fromStdString(table.get(name)); + h.set(QString::fromStdString(name), str); + } + else if (table.get(name).isBoolean()) { + AMQP::Field &b = const_cast(table.get(name)); + AMQP::BooleanSet &b1 = dynamic_cast(b); + bool value = b1.get(0); + h.set(QString::fromStdString(name), value); + } + } + + rMsg->setHeaders(h); + + QThread::sleep(3); + emit onResultReady(rMsg, deliveryTag); + + channel->ack(deliveryTag); + }; + + // объявление точки обмена + + if (!consumeOption.receivingExchange.name.isEmpty()) { + string exchange = consumeOption.receivingExchange.name.toStdString(); + string type = consumeOption.receivingExchange.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + AMQP::ExchangeType typeEx; + if (type == "" || type == "direct") + typeEx = AMQP::direct; + else if (type == "topic") + typeEx = AMQP::topic; + else if (type == "headers") + typeEx = AMQP::headers; + else + typeEx = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + int flagsExchange = 0; + if (consumeOption.receivingExchange.durable) + flagsExchange |= AMQP::durable; + if (consumeOption.receivingExchange.auto_delete) + flagsExchange |= AMQP::autodelete; + if (consumeOption.receivingExchange.internal) + flagsExchange |= AMQP::internal; + + AMQP::Table tableExch; + QString alt_e_name = "alternate-exchange"; + QString alt_e_value = ""; + if (consumeOption.receivingExchange.arguments.contains(alt_e_name)) { + alt_e_value = consumeOption.receivingExchange.arguments[alt_e_name].value(); + tableExch.set(alt_e_name.toStdString(), alt_e_value.toStdString()); + } + + // Для предопределенных точек обмена их обьявление не производим + if ( exchange != "" && exchange != "amq.fanout" && exchange != "amq.direct" && + exchange != "amq.topic" && exchange != "amq.headers") { + channel->declareExchange(exchange, typeEx, flagsExchange, tableExch) + .onError([&](const char *description) { + qDebug() << description; + }); + } + + QMultiMap::iterator it = consumeOption.bindingArgs.begin(); + for(; it != consumeOption.bindingArgs.end(); ++it) { + channel->bindExchange(it.key().toStdString(), exchange, it.value().toStdString()).onError([&](const char *description) { + qDebug() << description; + }); + } + } + + // объявление очереди + + QueueOption option = consumeOption.queueOption; + + string exchange = consumeOption.exchange.toStdString(); + string queue = option.name.toStdString(); + + // Подготовка флагов для объявления очереди + int flagsQueue = 0; + if (option.durable) + flagsQueue |= AMQP::durable; + if (option.auto_delete) + flagsQueue |= AMQP::autodelete; + if (option.exclusive) + flagsQueue |= AMQP::exclusive; + + channel->declareQueue(queue, flagsQueue) + .onSuccess( [&](const string &name, uint32_t messageCount, uint32_t consumerCount) { + Q_UNUSED(messageCount) + Q_UNUSED(consumerCount) + queue = name; + if (exchange != "") + for (QString rk : consumeOption.bindingKeys) { + channel->bindQueue(exchange, queue, rk.toStdString()) + .onError( [&](const char *description) { + qDebug() << description; + }); + } + }); + + // Подготовка флагов потребления сообщений + int flagsConsume = 0; + if (consumeOption.nolocal) + flagsConsume |= AMQP::nolocal; + if (consumeOption.noack) + flagsConsume |= AMQP::noack; + if (consumeOption.exclusive) + flagsConsume |= AMQP::exclusive; + + + channel->consume(queue, flagsConsume).onReceived(messageCb) + .onSuccess( [&](const string& tag) { + nextTag = tag; + }) + .onError( [&](const char *description) { + emit onErrorConsume(description); + markStop = true; // Останов потока + }); + + //Цикл обработки событий + while(!markStop) { + + event_base_loop(evbase, EVLOOP_ONCE); + QCoreApplication::processEvents(); + } + + // Закроем канал и соединение + channel->close(); + connection->close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); + + delete channel; + delete connection; + + emit onWorkFinished(); // Посылаем сигнал о завершении работы +} + + +void CWorker::slotStop() +{ + markStop = true; + channel->cancel(nextTag); // Отменить потребление + + channel->close(); + connection->close(); + +} + +void CWorker::bind(QString exchange, QString key, bool ex) +{ + if (ex) channel->bindExchange(exchange.toStdString(), consumeOption.exchange.toStdString(), key.toStdString()); + else channel->bindQueue(exchange.toStdString(), consumeOption.queueOption.name.toStdString(), key.toStdString()); +} + +void CWorker::unbind(QString exchange, QString key, bool ex) +{ + if (ex) channel->unbindExchange(exchange.toStdString(), consumeOption.exchange.toStdString(), key.toStdString()); + else channel->unbindQueue(exchange.toStdString(), consumeOption.queueOption.name.toStdString(), key.toStdString()); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/cworker.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/cworker.h new file mode 100644 index 0000000..f367037 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/cworker.h @@ -0,0 +1,54 @@ +#ifndef CWORKER_H +#define CWORKER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + + +class CWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ConsumeOption consumeOption; + + bool markStop; + + AMQP::TcpConnection *connection; + AMQP::TcpChannel *channel; + + string nextTag; + +public: + CWorker(ClientOption& clientOption, ConsumeOption& consumeOption); + virtual ~CWorker(); + +public slots: + void doWork(); + void slotStop(); + void bind(QString exchange, QString key, bool ex); + void unbind(QString exchange, QString key, bool ex); + +signals: + void onResultReady(PtrProduceMessage msg, uint64_t deliveryTag); + void onErrorConsume(const char *description); + void onWorkFinished(); +}; + + +#endif // CWORKER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/headers.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/headers.cpp new file mode 100644 index 0000000..9f56cb9 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/headers.cpp @@ -0,0 +1,40 @@ +#include "headers.h" + +Headers::Headers() +{ + _headers.clear(); +} + +Headers::Headers(const Headers& h) +{ + this->_headers = h._headers; +} + +void Headers::operator=(const Headers& h) +{ + this->_headers = h._headers; +} + + +QMap Headers::getHeaders() const { return _headers; } + +QList Headers::keys() const { return _headers.keys(); } +QList Headers::values() const { return _headers.values(); } + +int Headers::size() const { return _headers.size(); } + +bool Headers::contains(const QString name) const { return _headers.contains(name); } + + +void Headers::set(const QString name, bool value) { _headers.insert(name, value); } +void Headers::set(const QString name, int value) { _headers.insert(name, value); } +void Headers::set(const QString name, QString str) { _headers.insert(name, str); } + +bool Headers::isBool(const QString name) const { return _headers[name].type() == QVariant::Bool; } +bool Headers::isInteger(const QString name) const { return _headers[name].type() == QVariant::Int; } +bool Headers::isString(const QString name) const { return _headers[name].type() == QVariant::String; } + +bool Headers::getBool(const QString name) const { return _headers[name].value(); } +int Headers::getInteger(const QString name) const { return _headers[name].value(); } +QString Headers::getString(const QString name) const { return _headers[name].value(); } + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/headers.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/headers.h new file mode 100644 index 0000000..336a88a --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/headers.h @@ -0,0 +1,45 @@ +#ifndef HEADERS_H +#define HEADERS_H + +#include +#include +#include +#include + +class Headers +{ +private: + QMap _headers; + +public: + Headers(); + Headers(const Headers& h); // Конструктор копирования + ~Headers() {} + + void operator=(const Headers& h); + + QMap getHeaders() const; + + QList keys() const; + QList values() const; + + int size() const; + + bool contains(const QString name) const; + + void set(const QString name, bool value); + void set(const QString name, int value); + void set(const QString name, QString str); + + bool isBool(const QString name) const; + bool isInteger(const QString name) const; + bool isString(const QString name) const; + + bool getBool(const QString name) const; + int getInteger(const QString name) const; + QString getString(const QString name) const; + +}; + + +#endif // HEADERS_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/clientoption.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/clientoption.h new file mode 100644 index 0000000..5bccc3e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/clientoption.h @@ -0,0 +1,45 @@ +#ifndef CLIENTOPTION_H +#define CLIENTOPTION_H + +#include + +// Значения по умолчанию +const QString DEFAULT_CPP_HOST = "localhost"; +const int DEFAULT_CPP_PORT = 5672; +const QString DEFAULT_CPP_USER = "guest"; +const QString DEFAULT_CPP_PASSWORD = "guest"; +const QString DEFAULT_CPP_VHOST = "/"; + + +struct ClientOption { + QString host; + int port; + QString user; + QString password; + QString vhost; + + ClientOption() { + host = DEFAULT_CPP_HOST; + port = DEFAULT_CPP_PORT; + user = DEFAULT_CPP_USER; + password = DEFAULT_CPP_PASSWORD; + vhost = DEFAULT_CPP_VHOST; + } + + ~ClientOption() {} + + ClientOption(const ClientOption& src) = default; // Конструктор копирования + ClientOption(ClientOption&& src) = default; // Конструктор перемещения + + ClientOption& operator=(const ClientOption rhs) // Оператор присваивания + { + host = rhs.host; + port = rhs.port; + user = rhs.user; + password = rhs.password; + vhost = rhs.vhost; + return *this; + } +}; + +#endif // CLIENTOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/consumeoption.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/consumeoption.h new file mode 100644 index 0000000..8aef020 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/consumeoption.h @@ -0,0 +1,52 @@ +#ifndef CONSUMEOPTION_H +#define CONSUMEOPTION_H + +#include +#include +#include + +#include "queueoption.h" +#include "exchangeoption.h" + +struct ConsumeOption +{ + QString exchange; // Имя точки обмена для связывания + QStringList bindingKeys; // ключи связи точки с очередью + + bool nolocal; + bool noack; + bool exclusive; + + QueueOption queueOption; // Параметры очереди + ExchangeOption receivingExchange; // Параметры новой принимающей очереди (по умолчанию новой не создаётся) + QMultiMap bindingArgs; // список связей для точки обмена (если создаётся новая точка) + + ConsumeOption() { + exchange = ""; + receivingExchange.name = ""; + + nolocal = false; + noack = false; + exclusive = false; + } + + ~ConsumeOption() {} + + ConsumeOption(const ConsumeOption& src) = default; // Конструктор копирования + ConsumeOption(ConsumeOption&& src) = default; // Конструктор перемещения + + ConsumeOption& operator=(const ConsumeOption rhs) // Оператор присваивания + { + exchange = rhs.exchange; + bindingKeys = rhs.bindingKeys; + nolocal = rhs.nolocal; + noack = rhs.noack; + exclusive = rhs.exclusive; + queueOption = rhs.queueOption; + bindingArgs = rhs.bindingArgs; + return *this; + } + +}; + +#endif // CONSUMEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/exchangeoption.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/exchangeoption.h new file mode 100644 index 0000000..2c39cd2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/exchangeoption.h @@ -0,0 +1,55 @@ +#ifndef EXCHANGEOPTION_H +#define EXCHANGEOPTION_H + +#include +#include +#include + +struct ExchangeOption +{ + QString name; // уникальное имя точки обмена + QString type; // тип точки обмена (direct, topic, + // fanout или headers) + bool auto_delete; // автоматически удаляемая точка обмена + bool durable; // долгоживущая точка обмена + bool passive; // требуется информация о точке обмена + bool internal; // нельзя вести публикацию из приложения + + QVariantMap arguments; // необязательные аргументы + QMap bindingArgs; // список связей для точки обмена + + bool ifunused; // можно удалять, только если точка обмена + // не используется (не имеет потребителей) + ExchangeOption() { + name = ""; + type = ""; + auto_delete = false; + durable = false; + passive = false; + internal = false; + arguments.clear(); + + ifunused = false; + } + + ~ExchangeOption() {} + + ExchangeOption(const ExchangeOption& src) = default; // Конструктор копирования + ExchangeOption(ExchangeOption&& src) = default; // Конструктор перемещения + + ExchangeOption& operator=(const ExchangeOption rhs) // Оператор присваивания + { + name = rhs.name; + type = rhs.type; + auto_delete = rhs.auto_delete; + durable = rhs.durable; + passive = rhs.passive; + internal = rhs.internal; + arguments = rhs.arguments; + + ifunused = rhs.ifunused; + return *this; + } +}; + +#endif // EXCHANGEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/queueoption.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/queueoption.h new file mode 100644 index 0000000..2ca941e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/options/queueoption.h @@ -0,0 +1,63 @@ +#ifndef QUEUEOPTION_H +#define QUEUEOPTION_H + +#include +#include +#include + +struct QueueOption +{ + QString name; // Уникальное имя очереди + + bool auto_delete; // Автоматически удаляемая очередь + bool durable; // Долгоживущая очередь + bool passive; // Требуется информация об очереди + bool exclusive; // Исключительная очередь + + QVariantMap arguments; // Необязательные аргументы очереди + + bool ifunused; // Удалять, только если не используется + bool ifempty; // Удалять, только если очередь пуста + + int messageCount; // Число сообщений в очереди + int consumerCount; // Число потребителей очереди + + QueueOption() { + name = ""; + + auto_delete = false; + durable = false; + passive = false; + exclusive = false; + + arguments.clear(); + + ifunused = false; + ifempty = false; + + messageCount = 0; + consumerCount = 0; + } + + ~QueueOption() {} + + QueueOption(const QueueOption& src) = default; // Конструктор копирования + QueueOption(QueueOption&& src) = default; // Конструктор перемещения + + QueueOption& operator=(const QueueOption rhs) // Оператор присваивания + { + name = rhs.name; + auto_delete = rhs.auto_delete; + passive = rhs.passive; + exclusive = rhs.exclusive; + arguments = rhs.arguments; + + ifunused = rhs.ifunused; + ifempty = rhs.ifempty; + messageCount = rhs.messageCount; + consumerCount = rhs.consumerCount; + return *this; + } +}; + +#endif // QUEUEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/producemessage.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/producemessage.cpp new file mode 100644 index 0000000..95c28fc --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/producemessage.cpp @@ -0,0 +1,37 @@ +#include "producemessage.h" + +// Конструктор по умолчанию +ProduceMessage::ProduceMessage() +{ + _body.clear(); +} + +// Конструктор копирования +ProduceMessage::ProduceMessage(const ProduceMessage& msg) +{ + this->_body = msg._body; + this->_headers = msg._headers; + this->_properties = msg._properties; +} + +ProduceMessage& ProduceMessage::operator=(const ProduceMessage& msg) +{ + if (this != &msg) { + this->_body = msg._body; + this->_headers = msg._headers; + this->_properties = msg._properties; + } + return *this; +} + +QByteArray ProduceMessage::getBodyMsg() const { return _body; } +void ProduceMessage::setBodyMsg(const QByteArray &ba) { _body = ba; } +void ProduceMessage::setBodyMsg(const char *body, int size) { _body = QByteArray(body, size); } + +Headers ProduceMessage::getHeaders() const { return _headers; } +void ProduceMessage::setHeaders(const Headers &headers) { _headers = headers; } + +Properties ProduceMessage::getProperties() const { return _properties; } +void ProduceMessage::setProperties(const Properties &properties) { _properties = properties; } + + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/producemessage.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/producemessage.h new file mode 100644 index 0000000..bfb9413 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/producemessage.h @@ -0,0 +1,43 @@ +#ifndef PRODUCEMESSAGE_H +#define PRODUCEMESSAGE_H + +#include + +#include "properties.h" +#include "headers.h" + + +class ProduceMessage +{ +public: + ProduceMessage(); + ProduceMessage(const ProduceMessage& msg); + ~ProduceMessage() {} + + ProduceMessage& operator=(const ProduceMessage& msg); + + QByteArray getBodyMsg() const; + void setBodyMsg(const QByteArray &ba); + void setBodyMsg(const char *body, int size); + + Headers getHeaders() const; + void setHeaders(const Headers &headers); + + Properties getProperties() const; + void setProperties(const Properties &properties); + +private: + QByteArray _body; + +protected: + Properties _properties; + Headers _headers; +}; + + +using PtrProduceMessage = ProduceMessage*; + +Q_DECLARE_METATYPE(ProduceMessage) +Q_DECLARE_METATYPE(PtrProduceMessage) + +#endif // PRODUCEMESSAGE_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/properties.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/properties.cpp new file mode 100644 index 0000000..534eaa6 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/properties.cpp @@ -0,0 +1,63 @@ +#include "properties.h" + +Properties::Properties() +{ + _properties.clear(); + setDeliveryMode(1); // не оставлять сообщения +} + +// Конструктор копирования +Properties::Properties(const Properties& p) +{ + this->_properties = p._properties; +} + + +void Properties::operator=(const Properties& p) +{ + this->_properties = p._properties; +} + + +const QMap &Properties::getProperties() { return _properties; } + +bool Properties::contains(const QString name) const { return _properties.contains(name); } + +bool Properties::isContentType() const { return _properties.contains("content-type"); } +bool Properties::isContentEncoding() const { return _properties.contains("content-encoding"); } +bool Properties::isMessageID() const { return _properties.contains("message-id"); } +bool Properties::isCorrelationID() const { return _properties.contains("correlation-id"); } +bool Properties::isTimestamp() const { return _properties.contains("timestamp"); } +bool Properties::isExpiration() const { return _properties.contains("expiration"); } +bool Properties::isDeliveryMode() const { return _properties.contains("delivery-mode"); } +bool Properties::isAppID() const { return _properties.contains("app-id"); } +bool Properties::isUserID() const { return _properties.contains("user-id"); } +bool Properties::isTypeName() const { return _properties.contains("type"); } +bool Properties::isReplyTo() const { return _properties.contains("reply-to"); } +bool Properties::isPriority() const { return _properties.contains("priority"); } + +void Properties::setContentType(const QString &str) { _properties.insert("content-type", str); } +void Properties::setContentEncoding(const QString &str) { _properties.insert("content-encoding", str); } +void Properties::setMessageID(const QString &str) { _properties.insert("message-id", str); } +void Properties::setCorrelationID(const QString &str) { _properties.insert("correlation-id", str); } +void Properties::setTimestamp(const quint64 val) { _properties.insert("timestamp", val); } +void Properties::setExpiration(const QString &str) { _properties.insert("expiration", str); } +void Properties::setDeliveryMode(const quint8 val) { _properties.insert("delivery-mode", val); } +void Properties::setAppID(const QString &str) { _properties.insert("app-id", str); } +void Properties::setUserID(const QString &str) { _properties.insert("user-id", str); } +void Properties::setTypeName(const QString &str) { _properties.insert("type", str); } +void Properties::setReplyTo(const QString &str) { _properties.insert("reply-to", str); } +void Properties::setPriority(const quint8 val) { _properties.insert("priority", val); } + +QString Properties::getContentType() const { return _properties["content-type"].value(); } +QString Properties::getContentEncoding() const { return _properties["content-encoding"].value(); } +QString Properties::getMessageID() const { return _properties["message-id"].value(); } +QString Properties::getCorrelationID() const { return _properties["correlation-id"].value(); } +quint64 Properties::getTimestamp() const { return _properties["timestamp"].value(); } +QString Properties::getExpiration() const { return _properties["expiration"].value(); } +quint8 Properties::getDeliveryMode() const { return _properties["delivery-mode"].value(); } +QString Properties::getAppID() const { return _properties["app-id"].value(); } +QString Properties::getUserID() const { return _properties["user-id"].value(); } +QString Properties::getTypeName() const { return _properties["type"].value(); } +QString Properties::getReplyTo() const { return _properties["reply-to"].value(); } +quint8 Properties::getPriority() const { return _properties["priority"].value(); } diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/properties.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/properties.h new file mode 100644 index 0000000..d8a19f4 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/properties.h @@ -0,0 +1,73 @@ +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include + +#include +#include +#include + +class Properties +{ +private: + QMap _properties; + +public: + Properties(); + Properties(const Properties& p); + ~Properties() {} + + void operator=(const Properties& p); + + int size() {return _properties.size(); } + + const QMap &getProperties(); + + bool contains(const QString name) const; + + bool isContentType() const; + bool isContentEncoding() const; + bool isMessageID() const; + bool isCorrelationID() const; + bool isTimestamp() const; + bool isExpiration() const; + bool isDeliveryMode() const; + bool isAppID() const; + bool isUserID() const; + bool isTypeName() const; + bool isReplyTo() const; + bool isPriority() const; + + void setContentType(const QString &str); + void setContentEncoding(const QString &str); + void setMessageID(const QString &str); + void setCorrelationID(const QString &str); + void setTimestamp(const quint64 val); + void setExpiration(const QString &str); + void setDeliveryMode(const quint8 val); + void setAppID(const QString &str); + void setUserID(const QString &str); + void setTypeName(const QString &str); + void setReplyTo(const QString &str); + void setPriority(const quint8 val); + + QString getContentType() const; + QString getContentEncoding() const; + QString getMessageID() const; + QString getCorrelationID() const; + quint64 getTimestamp() const; + QString getExpiration() const; + quint8 getDeliveryMode() const; + QString getAppID() const; + QString getUserID() const; + QString getTypeName() const; + QString getReplyTo() const; + quint8 getPriority() const; +}; + +#endif // PROPERTIES_H + + + + + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/pworker.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/pworker.cpp new file mode 100644 index 0000000..6be4305 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/pworker.cpp @@ -0,0 +1,330 @@ +/************************************************************************** + * PWorker - Publish Worker, - рабочий поток публикации сообщений * + * редакция от 08.06.2022 * + * Принадлежность: библиотека clientRBcpp * + **************************************************************************/ + +#include + +#include "_client_.h" +#include "sender.h" +#include "pworker.h" +#include + + +PWorker::PWorker(ClientOption& clientOption, ExchangeOption& exchangeOption) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->exchangeOption = exchangeOption; + + qu.clear(); + +// static const int idE2E = qRegisterMetaType(); +// Q_UNUSED(idE2E) + +} + + +PWorker::~PWorker() +{ + // Освободим очередь сообщений + mutex.lock(); + while (! qu.isEmpty()) { + PublishPacket *packet = qu.dequeue(); + AMQP::Envelope *envelope = packet->envelope; + delete envelope->body(); + delete envelope; + delete packet; + } + mutex.unlock(); +} + + +// Здесь реализуется основная деятельность потока +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void PWorker::doWork() +{ + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + // Обрабатываем аргументы на предмет альтернативной точки обмена + AMQP::Table table; + QString alt_e_name = "alternate-exchange"; + QString alt_e_value = ""; + if (exchangeOption.arguments.contains(alt_e_name)) { + alt_e_value = exchangeOption.arguments[alt_e_name].value(); + table.set(alt_e_name.toStdString(), alt_e_value.toStdString()); + } + + string alt_exchange = alt_e_value.toStdString(); // Имя альтернативной точки обмена + AMQP::ExchangeType typeAltEx = AMQP::fanout; // Тип альтернативной точки обмена - всегда fanout + int flagsAltEx = (AMQP::durable | AMQP::internal); // Точка долгоживущая и внутренняя + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + AMQP::TcpConnection connection(&handler, AMQP::Address(address)); + AMQP::TcpChannel channel(&connection); + + channel.setQos(1); + + channel.confirmSelect() + .onAck([&](uint64_t deliveryTag, bool multiple) { + emit onReceivedAckNack(deliveryTag, true, multiple); + }) + .onNack([&](uint64_t deliveryTag, bool multiple, bool requeue) { + Q_UNUSED(requeue) + emit onReceivedAckNack(deliveryTag, false, multiple); + }); + + // Объявляем альтернативную точку обмена + //-------------------------------------- + if (alt_e_value != "") { + channel.declareExchange(alt_exchange, typeAltEx, flagsAltEx) + .onError( [&](const char *message) { + string msg(message); + emit onError(msg); + }); + } + + + // Обработка основной точки обмена + //---------------------------------- + string exchange = exchangeOption.name.toStdString(); + string type = exchangeOption.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + AMQP::ExchangeType typeEx; + if (type == "" || type == "direct") + typeEx = AMQP::direct; + else if (type == "topic") + typeEx = AMQP::topic; + else if (type == "headers") + typeEx = AMQP::headers; + else + typeEx = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + int flagsExchange = 0; + if (exchangeOption.durable) + flagsExchange |= AMQP::durable; + if (exchangeOption.auto_delete) + flagsExchange |= AMQP::autodelete; + if (exchangeOption.internal) + flagsExchange |= AMQP::internal; + + // Для предопределенных точек обмена их обьявление не производим + if ( exchange != "" && exchange != "amq.fanout" && exchange != "amq.direct" && + exchange != "amq.topic" && exchange != "amq.headers") { + channel.declareExchange(exchange, typeEx, flagsExchange, table) + .onError( [&](const char *message) { + string msg(message); + emit onError(msg); + }); + } + + // обработка mandatory + + auto messageCb = [&](const AMQP::Message& message, int16_t code, const std::string &description) + { + Q_UNUSED(code) + Q_UNUSED(description) + + // Формируем принятое сообщениев формате ReceivedMessage + ProduceMessage *rMsg = new ProduceMessage; + rMsg->setBodyMsg(message.body(), message.bodySize()); + + // Формируем таблицу Properties свойств сообщения + Properties p; + if (message.hasContentType()) + p.setContentType(QString::fromStdString(message.contentType())); + if (message.hasContentEncoding()) + p.setContentEncoding(QString::fromStdString(message.contentEncoding())); + if (message.hasMessageID()) + p.setMessageID(QString::fromStdString(message.messageID())); + if (message.hasCorrelationID()) + p.setCorrelationID(QString::fromStdString(message.correlationID())); + if (message.timestamp()) + p.setTimestamp(message.timestamp()); + if (message.hasExpiration()) + p.setExpiration(QString::fromStdString(message.expiration())); + if (message.hasDeliveryMode()) + p.setDeliveryMode(message.deliveryMode()); + if (message.hasAppID()) + p.setAppID(QString::fromStdString(message.appID())); + if (message.hasUserID()) + p.setUserID(QString::fromStdString(message.userID())); + if (message.hasTypeName()) + p.setTypeName(QString::fromStdString(message.typeName())); + if (message.hasReplyTo()) + p.setReplyTo(QString::fromStdString(message.replyTo())); + if (message.hasPriority()) + p.setPriority(message.priority()); + + rMsg->setProperties(p); + + // Работа со свойствами Headers + Headers h; + AMQP::Table table = message.headers(); + vector keys = table.keys(); + + string name; + for(uint i = 0; i < keys.size(); i++) { + name = keys[i]; + if (table.get(name).isInteger()) { + int value = table.get(name); + h.set(QString::fromStdString(name), value); + } + else if (table.get(name).isString()) { + QString str = QString::fromStdString(table.get(name)); + h.set(QString::fromStdString(name), str); + } + else if (table.get(name).isBoolean()) { + AMQP::Field &b = const_cast(table.get(name)); + AMQP::BooleanSet &b1 = dynamic_cast(b); + bool value = b1.get(0); + h.set(QString::fromStdString(name), value); + } + } + + rMsg->setHeaders(h); + emit onMessageBounced(rMsg); + }; + + channel.recall().onReceived(messageCb); + + // Цикл событий (event loop) + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + while (! markStop) { + + // Обрабатываем очередь сообщений на передачу + if (! qu.isEmpty()) { + + mutex.lock(); + PublishPacket *packet = qu.dequeue(); + mutex.unlock(); + + AMQP::Envelope *envelope = packet->envelope; + int publishFlags = packet->publishFlags; + string routingKey = packet->routingKey; + + if (envelope->hasAppID() && envelope->appID() == HEARBEATS) + connection.heartbeat(); + else + channel.publish(exchange, routingKey, *envelope, publishFlags); + + delete envelope->body(); + delete envelope; + delete packet; + } + + event_base_loop(evbase, EVLOOP_NONBLOCK); + QCoreApplication::processEvents(); + QThread::msleep(0); + } //while + + // Закроем канал и соединение + channel.close(); + connection.close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); + + emit onWorkFinished(); // Посылаем сигнал о завершении работы +} + + + +// Прием данных, предразование в формат передачи +// и постановка в очередь на выдачу +void PWorker::sending(ProduceMessage msg, QString routingKey, bool mandatory) +{ + mutex.lock(); + + uint64_t size = msg.getBodyMsg().size(); + char *body = new char[size]; + memcpy(body, msg.getBodyMsg().data(), size); + + AMQP::Envelope *env = new AMQP::Envelope(body, size); + + // Готовим сообщение для отправки + Properties p = msg.getProperties(); + if (p.contains("content-type")) + env->setContentType(p.getContentType().toStdString().c_str()); + if (p.contains("content-encoding")) + env->setContentEncoding(p.getContentEncoding().toStdString().c_str()); + if (p.contains("message-id")) + env->setMessageID(p.getMessageID().toStdString().c_str()); + if (p.contains("correlation-id")) + env->setCorrelationID(p.getCorrelationID().toStdString().c_str()); + if (p.contains("timestamp")) + env->setTimestamp(p.getTimestamp()); + if (p.contains("expiration")) + env->setExpiration(p.getExpiration().toStdString().c_str()); + if (p.contains("delivery-mode")) + env->setDeliveryMode(p.getDeliveryMode()); + if (p.contains("app-id")) + env->setAppID(p.getAppID().toStdString().c_str()); + if (p.contains("user-id")) + env->setUserID(p.getUserID().toStdString().c_str()); + if (p.contains("type")) + env->setTypeName(p.getTypeName().toStdString().c_str()); + if (p.contains("reply-to")) + env->setReplyTo(p.getReplyTo().toStdString().c_str()); + if (p.contains("priority")) + env->setPriority(p.getPriority()); + + AMQP::Table table; + + Headers p2 = msg.getHeaders(); + QList k = p2.keys(); + QList v = p2.values(); + for (int i=0; i < p2.size(); i++) { + QString name = k[i]; + QVariant val = v[i]; + if (val.type() == QVariant::Int) { + AMQP::Long numb = val.value(); + table.set(name.toStdString(), numb.value()); + } + else if (val.type() == QVariant::String) { + QString str = val.value(); + AMQP::ShortString s(str.toStdString()); + table.set(name.toStdString(), s.value()); + } + else if (val.type() == QVariant::Bool) { + bool numb = val.value(); + table.set(name.toStdString(), numb); + } + } + env->setHeaders(table); + + int flags = 0; // флаги - в формат AMQP + if (mandatory) + flags |= AMQP::mandatory; + + string routing = routingKey.toStdString(); + + // формируем пакет для постановки в очередь + PublishPacket *pp = new PublishPacket; + pp->envelope = env; + pp->publishFlags = flags; + pp->routingKey = routing; + + qu.enqueue(pp); + mutex.unlock(); + +} + + + +void PWorker::stop() +{ + markStop = true; // завершить цикл обработки сообщений +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/pworker.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/pworker.h new file mode 100644 index 0000000..9e3e25f --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/pworker.h @@ -0,0 +1,58 @@ +#ifndef PWORKER_H +#define PWORKER_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + + + +struct PublishPacket { + AMQP::Envelope *envelope; + string routingKey; + int publishFlags; +}; + + + +class PWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ExchangeOption exchangeOption; + + bool markStop; + QMutex mutex; + QQueue qu; + +public: + PWorker(ClientOption& clientOption, ExchangeOption& exchangeOption); + virtual ~PWorker(); + +public slots: + void doWork(); + void sending(ProduceMessage msg, QString routingKey, bool mandatory=false); + void stop(); + +signals: + void onMessageBounced(PtrProduceMessage msg); + void onError(string msg); + void onReceivedAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void onWorkFinished(); +}; + + +#endif // PWORKER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/receiver.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/receiver.cpp new file mode 100644 index 0000000..2553676 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/receiver.cpp @@ -0,0 +1,65 @@ +#include + +#include "client_cpp.h" + + + +Receiver::Receiver(ClientOption &clientOption, ConsumeOption &consumeOption) + : QObject(nullptr) +{ + this->clientOption = clientOption; + this->consumeOption = consumeOption; + this->consumeOption.receivingExchange = consumeOption.receivingExchange; + this->consumeOption.bindingArgs = consumeOption.bindingArgs; + + CWorker *worker = new CWorker(this->clientOption, this->consumeOption); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &CWorker::doWork); + connect(&workerThread, &QThread::finished, worker, &CWorker::slotStop); + + // Автоматическое удаление объектов PWorker и QThread по окончании работы + connect(worker, &CWorker::onWorkFinished, worker, &CWorker::deleteLater); + + // Посылаемые потоку сигналы + connect(this, &Receiver::onStop, worker, &CWorker::slotStop, Qt::DirectConnection); + connect(this, &Receiver::doBind, worker, &CWorker::bind, Qt::DirectConnection); + connect(this, &Receiver::doUnbind, worker, &CWorker::unbind, Qt::DirectConnection); + + // Сигналы, принимаемые от потока + connect(worker, &CWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &CWorker::onResultReady, this, &Receiver::slotMsgReady, Qt::QueuedConnection); + connect(worker, &CWorker::onErrorConsume, this, &Receiver::slotErrorMsg, Qt::QueuedConnection); + + workerThread.start(); + +} + + +Receiver::~Receiver() +{ + workerThread.quit(); + workerThread.wait(); +} + +// При получении от потока сигнала о приеме сообщения +// выпускаем сигнал дальше +void Receiver::slotMsgReady(PtrProduceMessage msg, uint64_t deliveryTag) +{ + ProduceMessage message = *msg; + delete msg; + emit onMessage(message, deliveryTag); +} + +// При получении сигнала об ошибке транслируем его +void Receiver::slotErrorMsg(const char *description) +{ + QString str(description); + emit onError(str); +} + + +void Receiver::slotStop() +{ + emit onStop(); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/receiver.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/receiver.h new file mode 100644 index 0000000..11ceb7b --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/receiver.h @@ -0,0 +1,47 @@ +#ifndef RECEIVER_H +#define RECEIVER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "_client_.h" +#include "cworker.h" + +using namespace std; + + +class Receiver : public QObject +{ + Q_OBJECT + +private: + bool stop; + + ClientOption clientOption; + ConsumeOption consumeOption; + QThread workerThread; + +public: + Receiver(ClientOption& clientOption, ConsumeOption& consumeOption); + virtual ~Receiver(); + +public slots: + void slotMsgReady(PtrProduceMessage msg, uint64_t deliveryTag); + void slotErrorMsg(const char *description); + void slotStop(); + +signals: + void onMessage(ProduceMessage msg, uint64_t deliveryTag); + void onError(QString description); + void onStop(); + void doBind(QString exchange, QString key, bool ex); + void doUnbind(QString exchange, QString key, bool ex); + +}; + +#endif // RECEIVER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/sender.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/sender.cpp new file mode 100644 index 0000000..51cbfd9 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/sender.cpp @@ -0,0 +1,106 @@ +#include + +#include "client_cpp.h" + + +Sender::Sender(ClientOption& clientOption, ExchangeOption& exchangeOption) + : QObject(nullptr) +{ + this->clientOption = clientOption; + this->exchangeOption = exchangeOption; + + PWorker *worker = new PWorker(this->clientOption, this->exchangeOption); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &PWorker::doWork); + + // Автоматическое удаление объектов PWorker и QThread по окончании работы + connect(worker, &PWorker::onWorkFinished, worker, &PWorker::deleteLater); + + // Посылаемые потоку сигналы + connect(this, &Sender::onSend, worker, &PWorker::sending, Qt::QueuedConnection); + connect(this, &Sender::onStop, worker, &PWorker::stop, Qt::DirectConnection); + + // Сигналы, принимаемые от потока + connect(worker, &PWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &PWorker::onReceivedAckNack, this, &Sender::slotAckNack, Qt::QueuedConnection); + connect(worker, &PWorker::onError, this, &Sender::slotError, Qt::QueuedConnection); + connect(worker, &PWorker::onMessageBounced, this, &Sender::slotMsgBounced, Qt::QueuedConnection); + + workerThread.start(); + + // Запуск таймера для механизма сердцебиения + tm.stop(); + tm.setInterval(30000); // 0,5 мин + connect(&tm, &QTimer::timeout, this, &Sender::onTimer); + tm.start(); +} + + +Sender::~Sender() +{ + tm.stop(); + + workerThread.quit(); + workerThread.wait(); +} + + +// Периодическое подключение по таймеру (1 мин) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void Sender::onTimer() +{ + tm.stop(); + + // Формируем и отправляем служебное сообщение + // на проверку соединения с экземпляром RabbitMQ + + string str = "@@"; // содержимое не играет роли + ProduceMessage msg; + msg.setBodyMsg(str.c_str(), str.size()); + + Properties p; + QString hearbeats(HEARBEATS); + p.setAppID(hearbeats); // маркер служебного сообщения сердцебиения + msg.setProperties(p); + + QString routingKey = ""; + emit onSend(msg, routingKey); // сообщение передаем в поток для передачи серверу + + tm.start(); +} + +void Sender::slotMsgBounced(PtrProduceMessage msg) +{ + ProduceMessage message = *msg; + delete msg; + emit onMsgBounced(message); +} + +// Передаем сообщение в поток для выдачи +void Sender::send(ProduceMessage msg, QString routingKey, bool mandatory) +{ + emit onSend(msg, routingKey, mandatory); +} + + +// Прием подтверждения от потока о выдаче (или невыдаче) сообщения +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------~~~~~~~~~~~~~~------------ +void Sender::slotAckNack(uint64_t deliveryTag, bool ack, bool multiple) +{ + emit onReceivedAckNack(deliveryTag, ack, multiple); +} + + +void Sender::slotError(string msg) +{ + QString message = QString::fromStdString(msg); + + emit onError(message); +} + + +void Sender::slotStop() +{ + emit onStop(); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/sender.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/sender.h new file mode 100644 index 0000000..4506bd3 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/sender.h @@ -0,0 +1,58 @@ +#ifndef SENDER_H +#define SENDER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "client_cpp.h" + +using namespace std; + +class Client; +class PWorker; + + +class Sender : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ExchangeOption exchangeOption; + QThread workerThread; + QTimer tm; + +public: + Sender(ClientOption& clientOption, ExchangeOption& exchangeOption); + virtual ~Sender(); + + + void send(ProduceMessage msg, QString routingKey, bool mandatory=false); + +private slots: + void onTimer(); + +public slots: + void slotMsgBounced(PtrProduceMessage msg); + void slotStop(); + void slotAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void slotError(string msg); + +signals: + void onMsgBounced(ProduceMessage msg); + void onReceivedAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void onSend(ProduceMessage msg, QString routingKey, bool mandatory=false); // Отправка сообщения потоку + void onError(QString &msg); + void onStop(); + +}; + + +#endif // SENDER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/validator.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/validator.cpp new file mode 100644 index 0000000..e80db05 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/validator.cpp @@ -0,0 +1,54 @@ +#include "validator.h" + +#include + +Validator::Validator(ClientOption& clientOption, QList& exchangeOptions) + : QObject(nullptr) +{ + // класс запускает поток с валидатором, который в течение 5 секунд должен проверить инфраструктуру. + // любая ошибка приведёт к завершению работы программы. если ошибок не было перехвачено, через 5 секунд процесс завершается. + + this->clientOption = clientOption; + this->exchangeOptions = exchangeOptions; + + VWorker *worker = new VWorker(this->clientOption, this->exchangeOptions); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &VWorker::doWork); + + // Автоматическое удаление объектов VWorker и QThread по окончании работы + connect(worker, &VWorker::onWorkFinished, worker, &VWorker::deleteLater); + + // Сигналы, принимаемые от потока + connect(worker, &VWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &VWorker::onError, this, &Validator::slotError, Qt::QueuedConnection); + + workerThread.start(); + + // Запуск таймера для механизма сердцебиения + tm.stop(); + tm.setInterval(5000); // 5 сек + connect(&tm, &QTimer::timeout, worker, &VWorker::slotStop); + connect(&tm, &QTimer::timeout, this, &Validator::onTimer); + tm.start(); +} + +Validator::~Validator() +{ + tm.stop(); + + workerThread.quit(); +} + +void Validator::onTimer() +{ + tm.stop(); + + workerThread.quit(); +} + +void Validator::slotError(string msg) +{ + QString message = QString::fromStdString(msg); + emit onError(message); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/validator.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/validator.h new file mode 100644 index 0000000..a717e74 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/validator.h @@ -0,0 +1,41 @@ +#ifndef VALIDATOR_H +#define VALIDATOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "client_cpp.h" + +class Validator : public QObject +{ + Q_OBJECT + +public: + Validator(ClientOption& clientOption, QList& exchangeOptions); + virtual ~Validator(); + +private: + ClientOption clientOption; + QList exchangeOptions; + QThread workerThread; + QTimer tm; + +private slots: + void onTimer(); + +public slots: + void slotError(string msg); + +signals: + void onError(QString &msg); + void onStop(); +}; + +#endif // VALIDATOR_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/vworker.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/vworker.cpp new file mode 100644 index 0000000..f4b31f6 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/vworker.cpp @@ -0,0 +1,123 @@ +#include + +#include "_client_.h" +#include "sender.h" +#include "vworker.h" + +#include + +VWorker::VWorker(ClientOption &clientOption, QList& exchangeOptions) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->exchangeOptions = exchangeOptions; +} + +VWorker::~VWorker() +{ + +} + +void VWorker::doWork() +{ + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + // Создаём подключение + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + AMQP::TcpConnection connection(&handler, AMQP::Address(address)); + AMQP::TcpChannel channel(&connection); + + channel.setQos(1); + + channel.confirmSelect() + .onError([&](const char *message) { + qDebug() << "validator - connecting error: " << message; + }); + + // Обрабатываем список точек обмена + + string exch; + string exType; + AMQP::ExchangeType typeExch; + int flagsExch = 0; + + QString ex_alt_e_name = "alternate-exchange"; + QString ex_alt_e_value = ""; + + for(ExchangeOption exOpt : exchangeOptions) { + AMQP::Table exTable; + exch = exOpt.name.toStdString(); + exType = exOpt.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + if (exType == "" || exType == "direct") + typeExch = AMQP::direct; + else if (exType == "topic") + typeExch = AMQP::topic; + else if (exType == "headers") + typeExch = AMQP::headers; + else + typeExch = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + if (exOpt.durable) + flagsExch |= AMQP::durable; + if (exOpt.auto_delete) + flagsExch |= AMQP::autodelete; + if (exOpt.internal) + flagsExch |= AMQP::internal; + + if (exOpt.arguments.contains(ex_alt_e_name)) { + ex_alt_e_value = exOpt.arguments[ex_alt_e_name].value(); + exTable.set(ex_alt_e_name.toStdString(), ex_alt_e_value.toStdString()); + } + + //Для предопределенных точек обмена их обьявление не производим + + if ( exch != "" && exch != "amq.fanout" && exch != "amq.direct" && + exch != "amq.topic" && exch != "amq.headers") { + channel.declareExchange(exch, typeExch, flagsExch, exTable) + .onError( [&](const char *message) { + qDebug() << "validator - declaring error: " << message; + emit onError(message); + }); + } + + QMap::iterator it = exOpt.bindingArgs.begin(); + for (; it != exOpt.bindingArgs.end(); ++it) { + channel.bindExchange(exch, it.key().toStdString(), it.value().toStdString()) + .onError( [&](const char *message) { + qDebug() << "validator - binding error: " << message; + emit onError(message); + }); + } + } + + while (! markStop) { + event_base_loop(evbase, EVLOOP_NONBLOCK); + QCoreApplication::processEvents(); + QThread::msleep(0); + } + + // Закроем канал и соединение + channel.close(); + connection.close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); +} + +void VWorker::slotStop() +{ + markStop = true; +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/vworker.h b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/vworker.h new file mode 100644 index 0000000..2019489 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/clientRBcpp/vworker.h @@ -0,0 +1,43 @@ +#ifndef VWORKER_H +#define VWORKER_H + +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + +class VWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + QList exchangeOptions; + + bool markStop; + QMutex mutex; + +public: + VWorker(ClientOption& clientOption, QList& exchangeOptions); + virtual ~VWorker(); + +public slots: + void doWork(); + void slotStop(); + +signals: + void onError(string msg); + void onWorkFinished(); +}; + + +#endif // VWORKER_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro b/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro new file mode 100644 index 0000000..749c77b --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro @@ -0,0 +1,41 @@ +QT += core gui widgets + +LIBS += -L/usr/lib -lamqpcpp -L/usr/lib/x86_64-linux-gnu/ -levent -lpthread -ldl + +SOURCES += \ + clientRBcpp/client.cpp \ + clientRBcpp/clientrbcpp.cpp \ + clientRBcpp/cworker.cpp \ + clientRBcpp/headers.cpp \ + clientRBcpp/producemessage.cpp \ + clientRBcpp/properties.cpp \ + clientRBcpp/pworker.cpp \ + clientRBcpp/receiver.cpp \ + clientRBcpp/sender.cpp \ + clientRBcpp/validator.cpp \ + clientRBcpp/vworker.cpp \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + clientRBcpp/_client_.h \ + clientRBcpp/client_cpp.h \ + clientRBcpp/clientrbcpp.h \ + clientRBcpp/clientrbcpp_global.h \ + clientRBcpp/cworker.h \ + clientRBcpp/headers.h \ + clientRBcpp/options/clientoption.h \ + clientRBcpp/options/consumeoption.h \ + clientRBcpp/options/exchangeoption.h \ + clientRBcpp/options/queueoption.h \ + clientRBcpp/producemessage.h \ + clientRBcpp/properties.h \ + clientRBcpp/pworker.h \ + clientRBcpp/receiver.h \ + clientRBcpp/sender.h \ + clientRBcpp/validator.h \ + clientRBcpp/vworker.h \ + mainwindow.h + +FORMS += \ + mainwindow.ui diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro.user b/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro.user new file mode 100644 index 0000000..f8cbfd2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro.user @@ -0,0 +1,338 @@ + + + + + + EnvironmentId + {77607214-f3f8-45c8-bf65-1a310ea854a8} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + true + Builtin.Questionable + + true + Builtin.DefaultTidyAndClazy + 0 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + {fa463890-d98c-43fb-aee8-64b3da65bdfc} + 0 + 0 + 0 + + true + 0 + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Debug + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Debug + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Отладка + Qt4ProjectManager.Qt4BuildConfiguration + 2 + 2 + 2 + + + true + 2 + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Release + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Release + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Выпуск + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 2 + + + true + 0 + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Profile + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Profile + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Профилирование + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:/home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/consumer_slow/consumer_slow.pro + + false + + false + true + true + false + false + true + + /home/user/work/DAS_2023_1/alexandrov_dmitrii_lab_4/build-consumer_slow-Desktop-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/main.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.cpp b/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.cpp new file mode 100644 index 0000000..d0eebd8 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.cpp @@ -0,0 +1,33 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + ClientOption clOpt; + + ConsumeOption conOpt; + conOpt.exchange = "publisher"; + conOpt.bindingKeys << "all"; + conOpt.queueOption.name = "queue_slow"; + conOpt.queueOption.auto_delete = true; + + receiver = new Receiver(clOpt, conOpt); + QObject::connect(receiver, &Receiver::onMessage, this, [&](ProduceMessage msg, uint64_t consumeTag) { + Q_UNUSED(consumeTag) + + QString msg_body = "got " + QString::fromLocal8Bit(msg.getBodyMsg()); + ui->listWidget->addItem(msg_body); + }); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.h b/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.h new file mode 100644 index 0000000..a49b8b8 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.h @@ -0,0 +1,25 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "clientRBcpp/client_cpp.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + Receiver *receiver; + int counter; +}; +#endif // MAINWINDOW_H diff --git a/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.ui b/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.ui new file mode 100644 index 0000000..7e2eac5 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/consumer_slow/mainwindow.ui @@ -0,0 +1,37 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + + + + + 0 + 0 + 800 + 30 + + + + + + + + diff --git a/alexandrov_dmitrii_lab_4/consumer_slow_service b/alexandrov_dmitrii_lab_4/consumer_slow_service new file mode 100755 index 0000000..c0580e9 Binary files /dev/null and b/alexandrov_dmitrii_lab_4/consumer_slow_service differ diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/_client_.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/_client_.h new file mode 100644 index 0000000..98abf83 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/_client_.h @@ -0,0 +1,53 @@ +#ifndef _CLIENT_H +#define _CLIENT_H + +#include +#include +#include +#include +#include + +#include "options/clientoption.h" +#include "options/consumeoption.h" +#include "options/exchangeoption.h" + +using namespace std; + +#define HEARBEATS "@@__Control__@@" + +class Sender; +class Receiver; + +class Client : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + + QList *_senders; + QList *_receivers; + +public: + Client(); + Client(ClientOption option); + virtual ~Client(); + + Client& operator=(Client client); + QString getVersion() const; + + ClientOption getClientOption() const { return clientOption; } + + Sender *createSender(ExchangeOption& option); + void removeSender(Sender *sender); + + Receiver *createReceiver(ConsumeOption& option); + void removeReceiver(Receiver *receiver); + +signals: + void onStop(); +}; + +Q_DECLARE_METATYPE(string) + +#endif // _CLIENT_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/client.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/client.cpp new file mode 100644 index 0000000..3f905ba --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/client.cpp @@ -0,0 +1,124 @@ + +#include "_client_.h" +#include "sender.h" +#include "receiver.h" + + +// Конструктор для связи с локальным RabbitMQ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Client::Client() : QObject(nullptr) +{ + { + static const int idMsg = qRegisterMetaType(); + static const int idMsgPtr = qRegisterMetaType(); + static const int idString = qRegisterMetaType(); + + Q_UNUSED(idMsg) + Q_UNUSED(idMsgPtr) + Q_UNUSED(idString) + } + + _senders = new QList(); + _senders->clear(); + + _receivers = new QList(); + _receivers->clear(); + +} + + + +Client::Client(ClientOption option) : Client() +{ + clientOption = option; +} + + +Client::~Client() +{ + for (auto &sender : *_senders) + delete sender; + delete _senders; + + for (auto &receiver : *_receivers) + delete receiver; + delete _receivers; +} + + +Client& Client::operator=(Client client) +{ + if (this != &client) { + this->clientOption = client.clientOption; + + this->_senders = new QList(); + this->_senders->clear(); + + this->_receivers = new QList(); + this->_receivers->clear(); + } + + return *this; +} + + +int majorVersion = 1; +int minorVersion = 1; +int releaseVersion = 1; + +QString Client::getVersion() const +{ + return QString::number(majorVersion) + + "." + QString::number(minorVersion) + + "." + QString::number(releaseVersion); +} + + + +// Создание публикатора (издателя) сообщений +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sender *Client::createSender(ExchangeOption& option) +{ + Sender *sender = new Sender(clientOption, option); + + connect(this, &Client::onStop, sender, &Sender::slotStop); + connect(this, &Client::onStop, sender, &Sender::deleteLater); + + _senders->append(sender); + + return sender; +} + +void Client::removeSender(Sender *sender) +{ + if ( !_senders->contains(sender)) + return; + sender->slotStop(); + _senders->removeOne(sender); +} + + +// Создание потребителя сообщений +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Receiver *Client::createReceiver(ConsumeOption& option) +{ + Receiver *receiver = new Receiver(clientOption, option); + + connect(this, &Client::onStop, receiver, &Receiver::slotStop); + connect(this, &Client::onStop, receiver, &Receiver::deleteLater); + + _receivers->append(receiver); + + return receiver; +} + +void Client::removeReceiver(Receiver *receiver) +{ + if ( !_receivers->contains(receiver)) + return; + receiver->slotStop(); + _receivers->removeOne(receiver); +} + + diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/client_cpp.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/client_cpp.h new file mode 100644 index 0000000..04c35dc --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/client_cpp.h @@ -0,0 +1,22 @@ +#ifndef CLIENT_CPP_H +#define CLIENT_CPP_H + +#include "clientrbcpp_global.h" +#include "clientrbcpp.h" +#include "_client_.h" +#include "producemessage.h" +#include "properties.h" +#include "cworker.h" +#include "headers.h" +#include "pworker.h" +#include "receiver.h" +#include "sender.h" +#include "validator.h" +#include "vworker.h" + +#include "options/clientoption.h" +#include "options/consumeoption.h" +#include "options/exchangeoption.h" +#include "options/queueoption.h" + +#endif // CLIENT_CPP_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp.cpp new file mode 100644 index 0000000..06a2a25 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp.cpp @@ -0,0 +1,6 @@ +#include "clientrbcpp.h" + + +ClientRBcpp::ClientRBcpp() +{ +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp.h new file mode 100644 index 0000000..eba40e2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp.h @@ -0,0 +1,13 @@ +#ifndef CLIENTRBCPP_H +#define CLIENTRBCPP_H + +#include "clientrbcpp_global.h" + +class CLIENTRBCPPSHARED_EXPORT ClientRBcpp +{ + +public: + ClientRBcpp(); +}; + +#endif // CLIENTRBCPP_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp_global.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp_global.h new file mode 100644 index 0000000..6fa749e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/clientrbcpp_global.h @@ -0,0 +1,12 @@ +#ifndef CLIENTRBCPP_GLOBAL_H +#define CLIENTRBCPP_GLOBAL_H + +#include + +#if defined(CLIENTRBCPP_LIBRARY) +# define CLIENTRBCPPSHARED_EXPORT Q_DECL_EXPORT +#else +# define CLIENTRBCPPSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // CLIENTRBCPP_GLOBAL_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/cworker.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/cworker.cpp new file mode 100644 index 0000000..b27e98c --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/cworker.cpp @@ -0,0 +1,276 @@ +#include + +#include "client_cpp.h" + +#include + +CWorker::CWorker(ClientOption& clientOption, ConsumeOption& consumeOption) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->consumeOption = consumeOption; + this->consumeOption.receivingExchange = consumeOption.receivingExchange; + + connection = nullptr; + channel = nullptr; +} + + + +CWorker::~CWorker() +{ + +} + + +// Здесь реализуется основная деятельность потока +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void CWorker::doWork() +{ + + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + +// AMQP::TcpConnection connection(&handler, AMQP::Address(address)); +// AMQP::TcpChannel channel(&connection); + + connection = new AMQP::TcpConnection(&handler, AMQP::Address(address)); + channel = new AMQP::TcpChannel(connection); + + channel->setQos(1); + + channel->onError([&](const char *message) { + Q_UNUSED(message) + emit onErrorConsume("Channel error!!!"); + }); + + QTimer tm; + tm.stop(); + tm.setInterval(30000); + connect(&tm, &QTimer::timeout, this, [&]() { + tm.stop(); + connection->heartbeat(); + tm.start(); + }); + tm.start(); + + // Обработка принятого сообщения + auto messageCb = [&](const AMQP::Message& message, uint64_t deliveryTag, bool redelivered) + { + Q_UNUSED(redelivered) + + // Формируем принятое сообщениев формате ReceivedMessage + ProduceMessage *rMsg = new ProduceMessage; + rMsg->setBodyMsg(message.body(), message.bodySize()); + + // Формируем таблицу Properties свойств сообщения + Properties p; + if (message.hasContentType()) + p.setContentType(QString::fromStdString(message.contentType())); + if (message.hasContentEncoding()) + p.setContentEncoding(QString::fromStdString(message.contentEncoding())); + if (message.hasMessageID()) + p.setMessageID(QString::fromStdString(message.messageID())); + if (message.hasCorrelationID()) + p.setCorrelationID(QString::fromStdString(message.correlationID())); + if (message.timestamp()) + p.setTimestamp(message.timestamp()); + if (message.hasExpiration()) + p.setExpiration(QString::fromStdString(message.expiration())); + if (message.hasDeliveryMode()) + p.setDeliveryMode(message.deliveryMode()); + if (message.hasAppID()) + p.setAppID(QString::fromStdString(message.appID())); + if (message.hasUserID()) + p.setUserID(QString::fromStdString(message.userID())); + if (message.hasTypeName()) + p.setTypeName(QString::fromStdString(message.typeName())); + if (message.hasReplyTo()) + p.setReplyTo(QString::fromStdString(message.replyTo())); + if (message.hasPriority()) + p.setPriority(message.priority()); + + rMsg->setProperties(p); + + // Работа со свойствами Headers + Headers h; + AMQP::Table table = message.headers(); + vector keys = table.keys(); + + string name; + for(uint i = 0; i < keys.size(); i++) { + name = keys[i]; + if (table.get(name).isInteger()) { + int value = table.get(name); + h.set(QString::fromStdString(name), value); + } + else if (table.get(name).isString()) { + QString str = QString::fromStdString(table.get(name)); + h.set(QString::fromStdString(name), str); + } + else if (table.get(name).isBoolean()) { + AMQP::Field &b = const_cast(table.get(name)); + AMQP::BooleanSet &b1 = dynamic_cast(b); + bool value = b1.get(0); + h.set(QString::fromStdString(name), value); + } + } + + rMsg->setHeaders(h); + emit onResultReady(rMsg, deliveryTag); + + channel->ack(deliveryTag); + }; + + // объявление точки обмена + + if (!consumeOption.receivingExchange.name.isEmpty()) { + string exchange = consumeOption.receivingExchange.name.toStdString(); + string type = consumeOption.receivingExchange.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + AMQP::ExchangeType typeEx; + if (type == "" || type == "direct") + typeEx = AMQP::direct; + else if (type == "topic") + typeEx = AMQP::topic; + else if (type == "headers") + typeEx = AMQP::headers; + else + typeEx = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + int flagsExchange = 0; + if (consumeOption.receivingExchange.durable) + flagsExchange |= AMQP::durable; + if (consumeOption.receivingExchange.auto_delete) + flagsExchange |= AMQP::autodelete; + if (consumeOption.receivingExchange.internal) + flagsExchange |= AMQP::internal; + + AMQP::Table tableExch; + QString alt_e_name = "alternate-exchange"; + QString alt_e_value = ""; + if (consumeOption.receivingExchange.arguments.contains(alt_e_name)) { + alt_e_value = consumeOption.receivingExchange.arguments[alt_e_name].value(); + tableExch.set(alt_e_name.toStdString(), alt_e_value.toStdString()); + } + + // Для предопределенных точек обмена их обьявление не производим + if ( exchange != "" && exchange != "amq.fanout" && exchange != "amq.direct" && + exchange != "amq.topic" && exchange != "amq.headers") { + channel->declareExchange(exchange, typeEx, flagsExchange, tableExch) + .onError([&](const char *description) { + qDebug() << description; + }); + } + + QMultiMap::iterator it = consumeOption.bindingArgs.begin(); + for(; it != consumeOption.bindingArgs.end(); ++it) { + channel->bindExchange(it.key().toStdString(), exchange, it.value().toStdString()).onError([&](const char *description) { + qDebug() << description; + }); + } + } + + // объявление очереди + + QueueOption option = consumeOption.queueOption; + + string exchange = consumeOption.exchange.toStdString(); + string queue = option.name.toStdString(); + + // Подготовка флагов для объявления очереди + int flagsQueue = 0; + if (option.durable) + flagsQueue |= AMQP::durable; + if (option.auto_delete) + flagsQueue |= AMQP::autodelete; + if (option.exclusive) + flagsQueue |= AMQP::exclusive; + + channel->declareQueue(queue, flagsQueue) + .onSuccess( [&](const string &name, uint32_t messageCount, uint32_t consumerCount) { + Q_UNUSED(messageCount) + Q_UNUSED(consumerCount) + queue = name; + if (exchange != "") + for (QString rk : consumeOption.bindingKeys) { + channel->bindQueue(exchange, queue, rk.toStdString()) + .onError( [&](const char *description) { + qDebug() << description; + }); + } + }); + + // Подготовка флагов потребления сообщений + int flagsConsume = 0; + if (consumeOption.nolocal) + flagsConsume |= AMQP::nolocal; + if (consumeOption.noack) + flagsConsume |= AMQP::noack; + if (consumeOption.exclusive) + flagsConsume |= AMQP::exclusive; + + + channel->consume(queue, flagsConsume).onReceived(messageCb) + .onSuccess( [&](const string& tag) { + nextTag = tag; + }) + .onError( [&](const char *description) { + emit onErrorConsume(description); + markStop = true; // Останов потока + }); + + //Цикл обработки событий + while(!markStop) { + + event_base_loop(evbase, EVLOOP_ONCE); + QCoreApplication::processEvents(); + } + + // Закроем канал и соединение + channel->close(); + connection->close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); + + delete channel; + delete connection; + + emit onWorkFinished(); // Посылаем сигнал о завершении работы +} + + +void CWorker::slotStop() +{ + markStop = true; + channel->cancel(nextTag); // Отменить потребление + + channel->close(); + connection->close(); + +} + +void CWorker::bind(QString exchange, QString key, bool ex) +{ + if (ex) channel->bindExchange(exchange.toStdString(), consumeOption.exchange.toStdString(), key.toStdString()); + else channel->bindQueue(exchange.toStdString(), consumeOption.queueOption.name.toStdString(), key.toStdString()); +} + +void CWorker::unbind(QString exchange, QString key, bool ex) +{ + if (ex) channel->unbindExchange(exchange.toStdString(), consumeOption.exchange.toStdString(), key.toStdString()); + else channel->unbindQueue(exchange.toStdString(), consumeOption.queueOption.name.toStdString(), key.toStdString()); +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/cworker.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/cworker.h new file mode 100644 index 0000000..f367037 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/cworker.h @@ -0,0 +1,54 @@ +#ifndef CWORKER_H +#define CWORKER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + + +class CWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ConsumeOption consumeOption; + + bool markStop; + + AMQP::TcpConnection *connection; + AMQP::TcpChannel *channel; + + string nextTag; + +public: + CWorker(ClientOption& clientOption, ConsumeOption& consumeOption); + virtual ~CWorker(); + +public slots: + void doWork(); + void slotStop(); + void bind(QString exchange, QString key, bool ex); + void unbind(QString exchange, QString key, bool ex); + +signals: + void onResultReady(PtrProduceMessage msg, uint64_t deliveryTag); + void onErrorConsume(const char *description); + void onWorkFinished(); +}; + + +#endif // CWORKER_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/headers.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/headers.cpp new file mode 100644 index 0000000..9f56cb9 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/headers.cpp @@ -0,0 +1,40 @@ +#include "headers.h" + +Headers::Headers() +{ + _headers.clear(); +} + +Headers::Headers(const Headers& h) +{ + this->_headers = h._headers; +} + +void Headers::operator=(const Headers& h) +{ + this->_headers = h._headers; +} + + +QMap Headers::getHeaders() const { return _headers; } + +QList Headers::keys() const { return _headers.keys(); } +QList Headers::values() const { return _headers.values(); } + +int Headers::size() const { return _headers.size(); } + +bool Headers::contains(const QString name) const { return _headers.contains(name); } + + +void Headers::set(const QString name, bool value) { _headers.insert(name, value); } +void Headers::set(const QString name, int value) { _headers.insert(name, value); } +void Headers::set(const QString name, QString str) { _headers.insert(name, str); } + +bool Headers::isBool(const QString name) const { return _headers[name].type() == QVariant::Bool; } +bool Headers::isInteger(const QString name) const { return _headers[name].type() == QVariant::Int; } +bool Headers::isString(const QString name) const { return _headers[name].type() == QVariant::String; } + +bool Headers::getBool(const QString name) const { return _headers[name].value(); } +int Headers::getInteger(const QString name) const { return _headers[name].value(); } +QString Headers::getString(const QString name) const { return _headers[name].value(); } + diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/headers.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/headers.h new file mode 100644 index 0000000..336a88a --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/headers.h @@ -0,0 +1,45 @@ +#ifndef HEADERS_H +#define HEADERS_H + +#include +#include +#include +#include + +class Headers +{ +private: + QMap _headers; + +public: + Headers(); + Headers(const Headers& h); // Конструктор копирования + ~Headers() {} + + void operator=(const Headers& h); + + QMap getHeaders() const; + + QList keys() const; + QList values() const; + + int size() const; + + bool contains(const QString name) const; + + void set(const QString name, bool value); + void set(const QString name, int value); + void set(const QString name, QString str); + + bool isBool(const QString name) const; + bool isInteger(const QString name) const; + bool isString(const QString name) const; + + bool getBool(const QString name) const; + int getInteger(const QString name) const; + QString getString(const QString name) const; + +}; + + +#endif // HEADERS_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/clientoption.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/clientoption.h new file mode 100644 index 0000000..5bccc3e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/clientoption.h @@ -0,0 +1,45 @@ +#ifndef CLIENTOPTION_H +#define CLIENTOPTION_H + +#include + +// Значения по умолчанию +const QString DEFAULT_CPP_HOST = "localhost"; +const int DEFAULT_CPP_PORT = 5672; +const QString DEFAULT_CPP_USER = "guest"; +const QString DEFAULT_CPP_PASSWORD = "guest"; +const QString DEFAULT_CPP_VHOST = "/"; + + +struct ClientOption { + QString host; + int port; + QString user; + QString password; + QString vhost; + + ClientOption() { + host = DEFAULT_CPP_HOST; + port = DEFAULT_CPP_PORT; + user = DEFAULT_CPP_USER; + password = DEFAULT_CPP_PASSWORD; + vhost = DEFAULT_CPP_VHOST; + } + + ~ClientOption() {} + + ClientOption(const ClientOption& src) = default; // Конструктор копирования + ClientOption(ClientOption&& src) = default; // Конструктор перемещения + + ClientOption& operator=(const ClientOption rhs) // Оператор присваивания + { + host = rhs.host; + port = rhs.port; + user = rhs.user; + password = rhs.password; + vhost = rhs.vhost; + return *this; + } +}; + +#endif // CLIENTOPTION_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/consumeoption.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/consumeoption.h new file mode 100644 index 0000000..8aef020 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/consumeoption.h @@ -0,0 +1,52 @@ +#ifndef CONSUMEOPTION_H +#define CONSUMEOPTION_H + +#include +#include +#include + +#include "queueoption.h" +#include "exchangeoption.h" + +struct ConsumeOption +{ + QString exchange; // Имя точки обмена для связывания + QStringList bindingKeys; // ключи связи точки с очередью + + bool nolocal; + bool noack; + bool exclusive; + + QueueOption queueOption; // Параметры очереди + ExchangeOption receivingExchange; // Параметры новой принимающей очереди (по умолчанию новой не создаётся) + QMultiMap bindingArgs; // список связей для точки обмена (если создаётся новая точка) + + ConsumeOption() { + exchange = ""; + receivingExchange.name = ""; + + nolocal = false; + noack = false; + exclusive = false; + } + + ~ConsumeOption() {} + + ConsumeOption(const ConsumeOption& src) = default; // Конструктор копирования + ConsumeOption(ConsumeOption&& src) = default; // Конструктор перемещения + + ConsumeOption& operator=(const ConsumeOption rhs) // Оператор присваивания + { + exchange = rhs.exchange; + bindingKeys = rhs.bindingKeys; + nolocal = rhs.nolocal; + noack = rhs.noack; + exclusive = rhs.exclusive; + queueOption = rhs.queueOption; + bindingArgs = rhs.bindingArgs; + return *this; + } + +}; + +#endif // CONSUMEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/exchangeoption.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/exchangeoption.h new file mode 100644 index 0000000..2c39cd2 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/exchangeoption.h @@ -0,0 +1,55 @@ +#ifndef EXCHANGEOPTION_H +#define EXCHANGEOPTION_H + +#include +#include +#include + +struct ExchangeOption +{ + QString name; // уникальное имя точки обмена + QString type; // тип точки обмена (direct, topic, + // fanout или headers) + bool auto_delete; // автоматически удаляемая точка обмена + bool durable; // долгоживущая точка обмена + bool passive; // требуется информация о точке обмена + bool internal; // нельзя вести публикацию из приложения + + QVariantMap arguments; // необязательные аргументы + QMap bindingArgs; // список связей для точки обмена + + bool ifunused; // можно удалять, только если точка обмена + // не используется (не имеет потребителей) + ExchangeOption() { + name = ""; + type = ""; + auto_delete = false; + durable = false; + passive = false; + internal = false; + arguments.clear(); + + ifunused = false; + } + + ~ExchangeOption() {} + + ExchangeOption(const ExchangeOption& src) = default; // Конструктор копирования + ExchangeOption(ExchangeOption&& src) = default; // Конструктор перемещения + + ExchangeOption& operator=(const ExchangeOption rhs) // Оператор присваивания + { + name = rhs.name; + type = rhs.type; + auto_delete = rhs.auto_delete; + durable = rhs.durable; + passive = rhs.passive; + internal = rhs.internal; + arguments = rhs.arguments; + + ifunused = rhs.ifunused; + return *this; + } +}; + +#endif // EXCHANGEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/queueoption.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/queueoption.h new file mode 100644 index 0000000..2ca941e --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/options/queueoption.h @@ -0,0 +1,63 @@ +#ifndef QUEUEOPTION_H +#define QUEUEOPTION_H + +#include +#include +#include + +struct QueueOption +{ + QString name; // Уникальное имя очереди + + bool auto_delete; // Автоматически удаляемая очередь + bool durable; // Долгоживущая очередь + bool passive; // Требуется информация об очереди + bool exclusive; // Исключительная очередь + + QVariantMap arguments; // Необязательные аргументы очереди + + bool ifunused; // Удалять, только если не используется + bool ifempty; // Удалять, только если очередь пуста + + int messageCount; // Число сообщений в очереди + int consumerCount; // Число потребителей очереди + + QueueOption() { + name = ""; + + auto_delete = false; + durable = false; + passive = false; + exclusive = false; + + arguments.clear(); + + ifunused = false; + ifempty = false; + + messageCount = 0; + consumerCount = 0; + } + + ~QueueOption() {} + + QueueOption(const QueueOption& src) = default; // Конструктор копирования + QueueOption(QueueOption&& src) = default; // Конструктор перемещения + + QueueOption& operator=(const QueueOption rhs) // Оператор присваивания + { + name = rhs.name; + auto_delete = rhs.auto_delete; + passive = rhs.passive; + exclusive = rhs.exclusive; + arguments = rhs.arguments; + + ifunused = rhs.ifunused; + ifempty = rhs.ifempty; + messageCount = rhs.messageCount; + consumerCount = rhs.consumerCount; + return *this; + } +}; + +#endif // QUEUEOPTION_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/producemessage.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/producemessage.cpp new file mode 100644 index 0000000..95c28fc --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/producemessage.cpp @@ -0,0 +1,37 @@ +#include "producemessage.h" + +// Конструктор по умолчанию +ProduceMessage::ProduceMessage() +{ + _body.clear(); +} + +// Конструктор копирования +ProduceMessage::ProduceMessage(const ProduceMessage& msg) +{ + this->_body = msg._body; + this->_headers = msg._headers; + this->_properties = msg._properties; +} + +ProduceMessage& ProduceMessage::operator=(const ProduceMessage& msg) +{ + if (this != &msg) { + this->_body = msg._body; + this->_headers = msg._headers; + this->_properties = msg._properties; + } + return *this; +} + +QByteArray ProduceMessage::getBodyMsg() const { return _body; } +void ProduceMessage::setBodyMsg(const QByteArray &ba) { _body = ba; } +void ProduceMessage::setBodyMsg(const char *body, int size) { _body = QByteArray(body, size); } + +Headers ProduceMessage::getHeaders() const { return _headers; } +void ProduceMessage::setHeaders(const Headers &headers) { _headers = headers; } + +Properties ProduceMessage::getProperties() const { return _properties; } +void ProduceMessage::setProperties(const Properties &properties) { _properties = properties; } + + diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/producemessage.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/producemessage.h new file mode 100644 index 0000000..bfb9413 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/producemessage.h @@ -0,0 +1,43 @@ +#ifndef PRODUCEMESSAGE_H +#define PRODUCEMESSAGE_H + +#include + +#include "properties.h" +#include "headers.h" + + +class ProduceMessage +{ +public: + ProduceMessage(); + ProduceMessage(const ProduceMessage& msg); + ~ProduceMessage() {} + + ProduceMessage& operator=(const ProduceMessage& msg); + + QByteArray getBodyMsg() const; + void setBodyMsg(const QByteArray &ba); + void setBodyMsg(const char *body, int size); + + Headers getHeaders() const; + void setHeaders(const Headers &headers); + + Properties getProperties() const; + void setProperties(const Properties &properties); + +private: + QByteArray _body; + +protected: + Properties _properties; + Headers _headers; +}; + + +using PtrProduceMessage = ProduceMessage*; + +Q_DECLARE_METATYPE(ProduceMessage) +Q_DECLARE_METATYPE(PtrProduceMessage) + +#endif // PRODUCEMESSAGE_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/properties.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/properties.cpp new file mode 100644 index 0000000..534eaa6 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/properties.cpp @@ -0,0 +1,63 @@ +#include "properties.h" + +Properties::Properties() +{ + _properties.clear(); + setDeliveryMode(1); // не оставлять сообщения +} + +// Конструктор копирования +Properties::Properties(const Properties& p) +{ + this->_properties = p._properties; +} + + +void Properties::operator=(const Properties& p) +{ + this->_properties = p._properties; +} + + +const QMap &Properties::getProperties() { return _properties; } + +bool Properties::contains(const QString name) const { return _properties.contains(name); } + +bool Properties::isContentType() const { return _properties.contains("content-type"); } +bool Properties::isContentEncoding() const { return _properties.contains("content-encoding"); } +bool Properties::isMessageID() const { return _properties.contains("message-id"); } +bool Properties::isCorrelationID() const { return _properties.contains("correlation-id"); } +bool Properties::isTimestamp() const { return _properties.contains("timestamp"); } +bool Properties::isExpiration() const { return _properties.contains("expiration"); } +bool Properties::isDeliveryMode() const { return _properties.contains("delivery-mode"); } +bool Properties::isAppID() const { return _properties.contains("app-id"); } +bool Properties::isUserID() const { return _properties.contains("user-id"); } +bool Properties::isTypeName() const { return _properties.contains("type"); } +bool Properties::isReplyTo() const { return _properties.contains("reply-to"); } +bool Properties::isPriority() const { return _properties.contains("priority"); } + +void Properties::setContentType(const QString &str) { _properties.insert("content-type", str); } +void Properties::setContentEncoding(const QString &str) { _properties.insert("content-encoding", str); } +void Properties::setMessageID(const QString &str) { _properties.insert("message-id", str); } +void Properties::setCorrelationID(const QString &str) { _properties.insert("correlation-id", str); } +void Properties::setTimestamp(const quint64 val) { _properties.insert("timestamp", val); } +void Properties::setExpiration(const QString &str) { _properties.insert("expiration", str); } +void Properties::setDeliveryMode(const quint8 val) { _properties.insert("delivery-mode", val); } +void Properties::setAppID(const QString &str) { _properties.insert("app-id", str); } +void Properties::setUserID(const QString &str) { _properties.insert("user-id", str); } +void Properties::setTypeName(const QString &str) { _properties.insert("type", str); } +void Properties::setReplyTo(const QString &str) { _properties.insert("reply-to", str); } +void Properties::setPriority(const quint8 val) { _properties.insert("priority", val); } + +QString Properties::getContentType() const { return _properties["content-type"].value(); } +QString Properties::getContentEncoding() const { return _properties["content-encoding"].value(); } +QString Properties::getMessageID() const { return _properties["message-id"].value(); } +QString Properties::getCorrelationID() const { return _properties["correlation-id"].value(); } +quint64 Properties::getTimestamp() const { return _properties["timestamp"].value(); } +QString Properties::getExpiration() const { return _properties["expiration"].value(); } +quint8 Properties::getDeliveryMode() const { return _properties["delivery-mode"].value(); } +QString Properties::getAppID() const { return _properties["app-id"].value(); } +QString Properties::getUserID() const { return _properties["user-id"].value(); } +QString Properties::getTypeName() const { return _properties["type"].value(); } +QString Properties::getReplyTo() const { return _properties["reply-to"].value(); } +quint8 Properties::getPriority() const { return _properties["priority"].value(); } diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/properties.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/properties.h new file mode 100644 index 0000000..d8a19f4 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/properties.h @@ -0,0 +1,73 @@ +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include + +#include +#include +#include + +class Properties +{ +private: + QMap _properties; + +public: + Properties(); + Properties(const Properties& p); + ~Properties() {} + + void operator=(const Properties& p); + + int size() {return _properties.size(); } + + const QMap &getProperties(); + + bool contains(const QString name) const; + + bool isContentType() const; + bool isContentEncoding() const; + bool isMessageID() const; + bool isCorrelationID() const; + bool isTimestamp() const; + bool isExpiration() const; + bool isDeliveryMode() const; + bool isAppID() const; + bool isUserID() const; + bool isTypeName() const; + bool isReplyTo() const; + bool isPriority() const; + + void setContentType(const QString &str); + void setContentEncoding(const QString &str); + void setMessageID(const QString &str); + void setCorrelationID(const QString &str); + void setTimestamp(const quint64 val); + void setExpiration(const QString &str); + void setDeliveryMode(const quint8 val); + void setAppID(const QString &str); + void setUserID(const QString &str); + void setTypeName(const QString &str); + void setReplyTo(const QString &str); + void setPriority(const quint8 val); + + QString getContentType() const; + QString getContentEncoding() const; + QString getMessageID() const; + QString getCorrelationID() const; + quint64 getTimestamp() const; + QString getExpiration() const; + quint8 getDeliveryMode() const; + QString getAppID() const; + QString getUserID() const; + QString getTypeName() const; + QString getReplyTo() const; + quint8 getPriority() const; +}; + +#endif // PROPERTIES_H + + + + + diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/pworker.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/pworker.cpp new file mode 100644 index 0000000..6be4305 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/pworker.cpp @@ -0,0 +1,330 @@ +/************************************************************************** + * PWorker - Publish Worker, - рабочий поток публикации сообщений * + * редакция от 08.06.2022 * + * Принадлежность: библиотека clientRBcpp * + **************************************************************************/ + +#include + +#include "_client_.h" +#include "sender.h" +#include "pworker.h" +#include + + +PWorker::PWorker(ClientOption& clientOption, ExchangeOption& exchangeOption) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->exchangeOption = exchangeOption; + + qu.clear(); + +// static const int idE2E = qRegisterMetaType(); +// Q_UNUSED(idE2E) + +} + + +PWorker::~PWorker() +{ + // Освободим очередь сообщений + mutex.lock(); + while (! qu.isEmpty()) { + PublishPacket *packet = qu.dequeue(); + AMQP::Envelope *envelope = packet->envelope; + delete envelope->body(); + delete envelope; + delete packet; + } + mutex.unlock(); +} + + +// Здесь реализуется основная деятельность потока +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void PWorker::doWork() +{ + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + // Обрабатываем аргументы на предмет альтернативной точки обмена + AMQP::Table table; + QString alt_e_name = "alternate-exchange"; + QString alt_e_value = ""; + if (exchangeOption.arguments.contains(alt_e_name)) { + alt_e_value = exchangeOption.arguments[alt_e_name].value(); + table.set(alt_e_name.toStdString(), alt_e_value.toStdString()); + } + + string alt_exchange = alt_e_value.toStdString(); // Имя альтернативной точки обмена + AMQP::ExchangeType typeAltEx = AMQP::fanout; // Тип альтернативной точки обмена - всегда fanout + int flagsAltEx = (AMQP::durable | AMQP::internal); // Точка долгоживущая и внутренняя + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + AMQP::TcpConnection connection(&handler, AMQP::Address(address)); + AMQP::TcpChannel channel(&connection); + + channel.setQos(1); + + channel.confirmSelect() + .onAck([&](uint64_t deliveryTag, bool multiple) { + emit onReceivedAckNack(deliveryTag, true, multiple); + }) + .onNack([&](uint64_t deliveryTag, bool multiple, bool requeue) { + Q_UNUSED(requeue) + emit onReceivedAckNack(deliveryTag, false, multiple); + }); + + // Объявляем альтернативную точку обмена + //-------------------------------------- + if (alt_e_value != "") { + channel.declareExchange(alt_exchange, typeAltEx, flagsAltEx) + .onError( [&](const char *message) { + string msg(message); + emit onError(msg); + }); + } + + + // Обработка основной точки обмена + //---------------------------------- + string exchange = exchangeOption.name.toStdString(); + string type = exchangeOption.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + AMQP::ExchangeType typeEx; + if (type == "" || type == "direct") + typeEx = AMQP::direct; + else if (type == "topic") + typeEx = AMQP::topic; + else if (type == "headers") + typeEx = AMQP::headers; + else + typeEx = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + int flagsExchange = 0; + if (exchangeOption.durable) + flagsExchange |= AMQP::durable; + if (exchangeOption.auto_delete) + flagsExchange |= AMQP::autodelete; + if (exchangeOption.internal) + flagsExchange |= AMQP::internal; + + // Для предопределенных точек обмена их обьявление не производим + if ( exchange != "" && exchange != "amq.fanout" && exchange != "amq.direct" && + exchange != "amq.topic" && exchange != "amq.headers") { + channel.declareExchange(exchange, typeEx, flagsExchange, table) + .onError( [&](const char *message) { + string msg(message); + emit onError(msg); + }); + } + + // обработка mandatory + + auto messageCb = [&](const AMQP::Message& message, int16_t code, const std::string &description) + { + Q_UNUSED(code) + Q_UNUSED(description) + + // Формируем принятое сообщениев формате ReceivedMessage + ProduceMessage *rMsg = new ProduceMessage; + rMsg->setBodyMsg(message.body(), message.bodySize()); + + // Формируем таблицу Properties свойств сообщения + Properties p; + if (message.hasContentType()) + p.setContentType(QString::fromStdString(message.contentType())); + if (message.hasContentEncoding()) + p.setContentEncoding(QString::fromStdString(message.contentEncoding())); + if (message.hasMessageID()) + p.setMessageID(QString::fromStdString(message.messageID())); + if (message.hasCorrelationID()) + p.setCorrelationID(QString::fromStdString(message.correlationID())); + if (message.timestamp()) + p.setTimestamp(message.timestamp()); + if (message.hasExpiration()) + p.setExpiration(QString::fromStdString(message.expiration())); + if (message.hasDeliveryMode()) + p.setDeliveryMode(message.deliveryMode()); + if (message.hasAppID()) + p.setAppID(QString::fromStdString(message.appID())); + if (message.hasUserID()) + p.setUserID(QString::fromStdString(message.userID())); + if (message.hasTypeName()) + p.setTypeName(QString::fromStdString(message.typeName())); + if (message.hasReplyTo()) + p.setReplyTo(QString::fromStdString(message.replyTo())); + if (message.hasPriority()) + p.setPriority(message.priority()); + + rMsg->setProperties(p); + + // Работа со свойствами Headers + Headers h; + AMQP::Table table = message.headers(); + vector keys = table.keys(); + + string name; + for(uint i = 0; i < keys.size(); i++) { + name = keys[i]; + if (table.get(name).isInteger()) { + int value = table.get(name); + h.set(QString::fromStdString(name), value); + } + else if (table.get(name).isString()) { + QString str = QString::fromStdString(table.get(name)); + h.set(QString::fromStdString(name), str); + } + else if (table.get(name).isBoolean()) { + AMQP::Field &b = const_cast(table.get(name)); + AMQP::BooleanSet &b1 = dynamic_cast(b); + bool value = b1.get(0); + h.set(QString::fromStdString(name), value); + } + } + + rMsg->setHeaders(h); + emit onMessageBounced(rMsg); + }; + + channel.recall().onReceived(messageCb); + + // Цикл событий (event loop) + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + while (! markStop) { + + // Обрабатываем очередь сообщений на передачу + if (! qu.isEmpty()) { + + mutex.lock(); + PublishPacket *packet = qu.dequeue(); + mutex.unlock(); + + AMQP::Envelope *envelope = packet->envelope; + int publishFlags = packet->publishFlags; + string routingKey = packet->routingKey; + + if (envelope->hasAppID() && envelope->appID() == HEARBEATS) + connection.heartbeat(); + else + channel.publish(exchange, routingKey, *envelope, publishFlags); + + delete envelope->body(); + delete envelope; + delete packet; + } + + event_base_loop(evbase, EVLOOP_NONBLOCK); + QCoreApplication::processEvents(); + QThread::msleep(0); + } //while + + // Закроем канал и соединение + channel.close(); + connection.close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); + + emit onWorkFinished(); // Посылаем сигнал о завершении работы +} + + + +// Прием данных, предразование в формат передачи +// и постановка в очередь на выдачу +void PWorker::sending(ProduceMessage msg, QString routingKey, bool mandatory) +{ + mutex.lock(); + + uint64_t size = msg.getBodyMsg().size(); + char *body = new char[size]; + memcpy(body, msg.getBodyMsg().data(), size); + + AMQP::Envelope *env = new AMQP::Envelope(body, size); + + // Готовим сообщение для отправки + Properties p = msg.getProperties(); + if (p.contains("content-type")) + env->setContentType(p.getContentType().toStdString().c_str()); + if (p.contains("content-encoding")) + env->setContentEncoding(p.getContentEncoding().toStdString().c_str()); + if (p.contains("message-id")) + env->setMessageID(p.getMessageID().toStdString().c_str()); + if (p.contains("correlation-id")) + env->setCorrelationID(p.getCorrelationID().toStdString().c_str()); + if (p.contains("timestamp")) + env->setTimestamp(p.getTimestamp()); + if (p.contains("expiration")) + env->setExpiration(p.getExpiration().toStdString().c_str()); + if (p.contains("delivery-mode")) + env->setDeliveryMode(p.getDeliveryMode()); + if (p.contains("app-id")) + env->setAppID(p.getAppID().toStdString().c_str()); + if (p.contains("user-id")) + env->setUserID(p.getUserID().toStdString().c_str()); + if (p.contains("type")) + env->setTypeName(p.getTypeName().toStdString().c_str()); + if (p.contains("reply-to")) + env->setReplyTo(p.getReplyTo().toStdString().c_str()); + if (p.contains("priority")) + env->setPriority(p.getPriority()); + + AMQP::Table table; + + Headers p2 = msg.getHeaders(); + QList k = p2.keys(); + QList v = p2.values(); + for (int i=0; i < p2.size(); i++) { + QString name = k[i]; + QVariant val = v[i]; + if (val.type() == QVariant::Int) { + AMQP::Long numb = val.value(); + table.set(name.toStdString(), numb.value()); + } + else if (val.type() == QVariant::String) { + QString str = val.value(); + AMQP::ShortString s(str.toStdString()); + table.set(name.toStdString(), s.value()); + } + else if (val.type() == QVariant::Bool) { + bool numb = val.value(); + table.set(name.toStdString(), numb); + } + } + env->setHeaders(table); + + int flags = 0; // флаги - в формат AMQP + if (mandatory) + flags |= AMQP::mandatory; + + string routing = routingKey.toStdString(); + + // формируем пакет для постановки в очередь + PublishPacket *pp = new PublishPacket; + pp->envelope = env; + pp->publishFlags = flags; + pp->routingKey = routing; + + qu.enqueue(pp); + mutex.unlock(); + +} + + + +void PWorker::stop() +{ + markStop = true; // завершить цикл обработки сообщений +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/pworker.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/pworker.h new file mode 100644 index 0000000..9e3e25f --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/pworker.h @@ -0,0 +1,58 @@ +#ifndef PWORKER_H +#define PWORKER_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + + + +struct PublishPacket { + AMQP::Envelope *envelope; + string routingKey; + int publishFlags; +}; + + + +class PWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ExchangeOption exchangeOption; + + bool markStop; + QMutex mutex; + QQueue qu; + +public: + PWorker(ClientOption& clientOption, ExchangeOption& exchangeOption); + virtual ~PWorker(); + +public slots: + void doWork(); + void sending(ProduceMessage msg, QString routingKey, bool mandatory=false); + void stop(); + +signals: + void onMessageBounced(PtrProduceMessage msg); + void onError(string msg); + void onReceivedAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void onWorkFinished(); +}; + + +#endif // PWORKER_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/receiver.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/receiver.cpp new file mode 100644 index 0000000..2553676 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/receiver.cpp @@ -0,0 +1,65 @@ +#include + +#include "client_cpp.h" + + + +Receiver::Receiver(ClientOption &clientOption, ConsumeOption &consumeOption) + : QObject(nullptr) +{ + this->clientOption = clientOption; + this->consumeOption = consumeOption; + this->consumeOption.receivingExchange = consumeOption.receivingExchange; + this->consumeOption.bindingArgs = consumeOption.bindingArgs; + + CWorker *worker = new CWorker(this->clientOption, this->consumeOption); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &CWorker::doWork); + connect(&workerThread, &QThread::finished, worker, &CWorker::slotStop); + + // Автоматическое удаление объектов PWorker и QThread по окончании работы + connect(worker, &CWorker::onWorkFinished, worker, &CWorker::deleteLater); + + // Посылаемые потоку сигналы + connect(this, &Receiver::onStop, worker, &CWorker::slotStop, Qt::DirectConnection); + connect(this, &Receiver::doBind, worker, &CWorker::bind, Qt::DirectConnection); + connect(this, &Receiver::doUnbind, worker, &CWorker::unbind, Qt::DirectConnection); + + // Сигналы, принимаемые от потока + connect(worker, &CWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &CWorker::onResultReady, this, &Receiver::slotMsgReady, Qt::QueuedConnection); + connect(worker, &CWorker::onErrorConsume, this, &Receiver::slotErrorMsg, Qt::QueuedConnection); + + workerThread.start(); + +} + + +Receiver::~Receiver() +{ + workerThread.quit(); + workerThread.wait(); +} + +// При получении от потока сигнала о приеме сообщения +// выпускаем сигнал дальше +void Receiver::slotMsgReady(PtrProduceMessage msg, uint64_t deliveryTag) +{ + ProduceMessage message = *msg; + delete msg; + emit onMessage(message, deliveryTag); +} + +// При получении сигнала об ошибке транслируем его +void Receiver::slotErrorMsg(const char *description) +{ + QString str(description); + emit onError(str); +} + + +void Receiver::slotStop() +{ + emit onStop(); +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/receiver.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/receiver.h new file mode 100644 index 0000000..11ceb7b --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/receiver.h @@ -0,0 +1,47 @@ +#ifndef RECEIVER_H +#define RECEIVER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "_client_.h" +#include "cworker.h" + +using namespace std; + + +class Receiver : public QObject +{ + Q_OBJECT + +private: + bool stop; + + ClientOption clientOption; + ConsumeOption consumeOption; + QThread workerThread; + +public: + Receiver(ClientOption& clientOption, ConsumeOption& consumeOption); + virtual ~Receiver(); + +public slots: + void slotMsgReady(PtrProduceMessage msg, uint64_t deliveryTag); + void slotErrorMsg(const char *description); + void slotStop(); + +signals: + void onMessage(ProduceMessage msg, uint64_t deliveryTag); + void onError(QString description); + void onStop(); + void doBind(QString exchange, QString key, bool ex); + void doUnbind(QString exchange, QString key, bool ex); + +}; + +#endif // RECEIVER_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/sender.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/sender.cpp new file mode 100644 index 0000000..51cbfd9 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/sender.cpp @@ -0,0 +1,106 @@ +#include + +#include "client_cpp.h" + + +Sender::Sender(ClientOption& clientOption, ExchangeOption& exchangeOption) + : QObject(nullptr) +{ + this->clientOption = clientOption; + this->exchangeOption = exchangeOption; + + PWorker *worker = new PWorker(this->clientOption, this->exchangeOption); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &PWorker::doWork); + + // Автоматическое удаление объектов PWorker и QThread по окончании работы + connect(worker, &PWorker::onWorkFinished, worker, &PWorker::deleteLater); + + // Посылаемые потоку сигналы + connect(this, &Sender::onSend, worker, &PWorker::sending, Qt::QueuedConnection); + connect(this, &Sender::onStop, worker, &PWorker::stop, Qt::DirectConnection); + + // Сигналы, принимаемые от потока + connect(worker, &PWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &PWorker::onReceivedAckNack, this, &Sender::slotAckNack, Qt::QueuedConnection); + connect(worker, &PWorker::onError, this, &Sender::slotError, Qt::QueuedConnection); + connect(worker, &PWorker::onMessageBounced, this, &Sender::slotMsgBounced, Qt::QueuedConnection); + + workerThread.start(); + + // Запуск таймера для механизма сердцебиения + tm.stop(); + tm.setInterval(30000); // 0,5 мин + connect(&tm, &QTimer::timeout, this, &Sender::onTimer); + tm.start(); +} + + +Sender::~Sender() +{ + tm.stop(); + + workerThread.quit(); + workerThread.wait(); +} + + +// Периодическое подключение по таймеру (1 мин) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void Sender::onTimer() +{ + tm.stop(); + + // Формируем и отправляем служебное сообщение + // на проверку соединения с экземпляром RabbitMQ + + string str = "@@"; // содержимое не играет роли + ProduceMessage msg; + msg.setBodyMsg(str.c_str(), str.size()); + + Properties p; + QString hearbeats(HEARBEATS); + p.setAppID(hearbeats); // маркер служебного сообщения сердцебиения + msg.setProperties(p); + + QString routingKey = ""; + emit onSend(msg, routingKey); // сообщение передаем в поток для передачи серверу + + tm.start(); +} + +void Sender::slotMsgBounced(PtrProduceMessage msg) +{ + ProduceMessage message = *msg; + delete msg; + emit onMsgBounced(message); +} + +// Передаем сообщение в поток для выдачи +void Sender::send(ProduceMessage msg, QString routingKey, bool mandatory) +{ + emit onSend(msg, routingKey, mandatory); +} + + +// Прием подтверждения от потока о выдаче (или невыдаче) сообщения +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------~~~~~~~~~~~~~~------------ +void Sender::slotAckNack(uint64_t deliveryTag, bool ack, bool multiple) +{ + emit onReceivedAckNack(deliveryTag, ack, multiple); +} + + +void Sender::slotError(string msg) +{ + QString message = QString::fromStdString(msg); + + emit onError(message); +} + + +void Sender::slotStop() +{ + emit onStop(); +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/sender.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/sender.h new file mode 100644 index 0000000..4506bd3 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/sender.h @@ -0,0 +1,58 @@ +#ifndef SENDER_H +#define SENDER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "client_cpp.h" + +using namespace std; + +class Client; +class PWorker; + + +class Sender : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + ExchangeOption exchangeOption; + QThread workerThread; + QTimer tm; + +public: + Sender(ClientOption& clientOption, ExchangeOption& exchangeOption); + virtual ~Sender(); + + + void send(ProduceMessage msg, QString routingKey, bool mandatory=false); + +private slots: + void onTimer(); + +public slots: + void slotMsgBounced(PtrProduceMessage msg); + void slotStop(); + void slotAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void slotError(string msg); + +signals: + void onMsgBounced(ProduceMessage msg); + void onReceivedAckNack(uint64_t deliveryTag, bool ack, bool multiple); + void onSend(ProduceMessage msg, QString routingKey, bool mandatory=false); // Отправка сообщения потоку + void onError(QString &msg); + void onStop(); + +}; + + +#endif // SENDER_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/validator.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/validator.cpp new file mode 100644 index 0000000..e80db05 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/validator.cpp @@ -0,0 +1,54 @@ +#include "validator.h" + +#include + +Validator::Validator(ClientOption& clientOption, QList& exchangeOptions) + : QObject(nullptr) +{ + // класс запускает поток с валидатором, который в течение 5 секунд должен проверить инфраструктуру. + // любая ошибка приведёт к завершению работы программы. если ошибок не было перехвачено, через 5 секунд процесс завершается. + + this->clientOption = clientOption; + this->exchangeOptions = exchangeOptions; + + VWorker *worker = new VWorker(this->clientOption, this->exchangeOptions); + worker->moveToThread(&workerThread); + + connect(&workerThread, &QThread::started, worker, &VWorker::doWork); + + // Автоматическое удаление объектов VWorker и QThread по окончании работы + connect(worker, &VWorker::onWorkFinished, worker, &VWorker::deleteLater); + + // Сигналы, принимаемые от потока + connect(worker, &VWorker::onWorkFinished, &workerThread, &QThread::quit, Qt::QueuedConnection); + connect(worker, &VWorker::onError, this, &Validator::slotError, Qt::QueuedConnection); + + workerThread.start(); + + // Запуск таймера для механизма сердцебиения + tm.stop(); + tm.setInterval(5000); // 5 сек + connect(&tm, &QTimer::timeout, worker, &VWorker::slotStop); + connect(&tm, &QTimer::timeout, this, &Validator::onTimer); + tm.start(); +} + +Validator::~Validator() +{ + tm.stop(); + + workerThread.quit(); +} + +void Validator::onTimer() +{ + tm.stop(); + + workerThread.quit(); +} + +void Validator::slotError(string msg) +{ + QString message = QString::fromStdString(msg); + emit onError(message); +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/validator.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/validator.h new file mode 100644 index 0000000..a717e74 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/validator.h @@ -0,0 +1,41 @@ +#ifndef VALIDATOR_H +#define VALIDATOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "client_cpp.h" + +class Validator : public QObject +{ + Q_OBJECT + +public: + Validator(ClientOption& clientOption, QList& exchangeOptions); + virtual ~Validator(); + +private: + ClientOption clientOption; + QList exchangeOptions; + QThread workerThread; + QTimer tm; + +private slots: + void onTimer(); + +public slots: + void slotError(string msg); + +signals: + void onError(QString &msg); + void onStop(); +}; + +#endif // VALIDATOR_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/vworker.cpp b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/vworker.cpp new file mode 100644 index 0000000..f4b31f6 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/vworker.cpp @@ -0,0 +1,123 @@ +#include + +#include "_client_.h" +#include "sender.h" +#include "vworker.h" + +#include + +VWorker::VWorker(ClientOption &clientOption, QList& exchangeOptions) + : QObject(nullptr), markStop(false) +{ + this->clientOption = clientOption; + this->exchangeOptions = exchangeOptions; +} + +VWorker::~VWorker() +{ + +} + +void VWorker::doWork() +{ + string host = clientOption.host.toStdString(); + string port = QString::number(clientOption.port).toStdString(); + string user = clientOption.user.toStdString(); + string password = clientOption.password.toStdString(); + string vhost = clientOption.vhost.toStdString(); + + string address = "amqp://" + user + ":" + password + "@" + host + ":" + port + vhost; + + // Создаём подключение + + auto evbase = event_base_new(); + + AMQP::LibEventHandler handler(evbase); + AMQP::TcpConnection connection(&handler, AMQP::Address(address)); + AMQP::TcpChannel channel(&connection); + + channel.setQos(1); + + channel.confirmSelect() + .onError([&](const char *message) { + qDebug() << "validator - connecting error: " << message; + }); + + // Обрабатываем список точек обмена + + string exch; + string exType; + AMQP::ExchangeType typeExch; + int flagsExch = 0; + + QString ex_alt_e_name = "alternate-exchange"; + QString ex_alt_e_value = ""; + + for(ExchangeOption exOpt : exchangeOptions) { + AMQP::Table exTable; + exch = exOpt.name.toStdString(); + exType = exOpt.type.toStdString(); + + // преобразование типа точки обмена в формат AMQP + if (exType == "" || exType == "direct") + typeExch = AMQP::direct; + else if (exType == "topic") + typeExch = AMQP::topic; + else if (exType == "headers") + typeExch = AMQP::headers; + else + typeExch = AMQP::fanout; + + // предобразование флагов точки обмена в формат AMQP + if (exOpt.durable) + flagsExch |= AMQP::durable; + if (exOpt.auto_delete) + flagsExch |= AMQP::autodelete; + if (exOpt.internal) + flagsExch |= AMQP::internal; + + if (exOpt.arguments.contains(ex_alt_e_name)) { + ex_alt_e_value = exOpt.arguments[ex_alt_e_name].value(); + exTable.set(ex_alt_e_name.toStdString(), ex_alt_e_value.toStdString()); + } + + //Для предопределенных точек обмена их обьявление не производим + + if ( exch != "" && exch != "amq.fanout" && exch != "amq.direct" && + exch != "amq.topic" && exch != "amq.headers") { + channel.declareExchange(exch, typeExch, flagsExch, exTable) + .onError( [&](const char *message) { + qDebug() << "validator - declaring error: " << message; + emit onError(message); + }); + } + + QMap::iterator it = exOpt.bindingArgs.begin(); + for (; it != exOpt.bindingArgs.end(); ++it) { + channel.bindExchange(exch, it.key().toStdString(), it.value().toStdString()) + .onError( [&](const char *message) { + qDebug() << "validator - binding error: " << message; + emit onError(message); + }); + } + } + + while (! markStop) { + event_base_loop(evbase, EVLOOP_NONBLOCK); + QCoreApplication::processEvents(); + QThread::msleep(0); + } + + // Закроем канал и соединение + channel.close(); + connection.close(); + + event_base_loopbreak(evbase); + event_base_loopexit(evbase, 0); + event_base_free(evbase); +} + +void VWorker::slotStop() +{ + markStop = true; +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/vworker.h b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/vworker.h new file mode 100644 index 0000000..2019489 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/clientRBcpp/vworker.h @@ -0,0 +1,43 @@ +#ifndef VWORKER_H +#define VWORKER_H + +#include +#include +#include +#include + +#include + +#include +#include + +#include "client_cpp.h" + +using namespace std; + +class VWorker : public QObject +{ + Q_OBJECT + +private: + ClientOption clientOption; + QList exchangeOptions; + + bool markStop; + QMutex mutex; + +public: + VWorker(ClientOption& clientOption, QList& exchangeOptions); + virtual ~VWorker(); + +public slots: + void doWork(); + void slotStop(); + +signals: + void onError(string msg); + void onWorkFinished(); +}; + + +#endif // VWORKER_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/main.cpp b/alexandrov_dmitrii_lab_4/publisher_app/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.cpp b/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.cpp new file mode 100644 index 0000000..b9b1e21 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.cpp @@ -0,0 +1,53 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + ClientOption clOpt; + + clOpt.host = "localhost"; + clOpt.port = 5672; + clOpt.user = "user"; + clOpt.password = "12345678"; + + Properties props; + props.setAppID("ClientApp"); + props.setDeliveryMode(1); + + pm.setProperties(props); + + ExchangeOption exOpt; + exOpt.name = "publisher"; + exOpt.type = "fanout"; + + sender = new Sender(clOpt, exOpt); + + tm = new QTimer(this); + connect(tm, SIGNAL(timeout()), this, SLOT(send_msg())); + tm->start(1000); + + counter = 1; +} + +MainWindow::~MainWindow() +{ + tm->stop(); + delete tm; + sender->slotStop(); + QThread::msleep(150); + delete sender; + delete ui; +} + +void MainWindow::send_msg() +{ + QString str = "message " + QString::number(counter); + ui->listWidget->addItem("sent " + str); + pm.setBodyMsg(QByteArray::fromStdString(str.toStdString())); + sender->send(pm, ""); + counter++; +} diff --git a/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.h b/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.h new file mode 100644 index 0000000..23dcbfe --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.h @@ -0,0 +1,31 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "clientRBcpp/client_cpp.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + Sender *sender; + ProduceMessage pm; + QTimer *tm; + int counter; + +private slots: + void send_msg(); + +}; +#endif // MAINWINDOW_H diff --git a/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.ui b/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.ui new file mode 100644 index 0000000..74862f7 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/mainwindow.ui @@ -0,0 +1,37 @@ + + + MainWindow + + + + 0 + 0 + 472 + 395 + + + + MainWindow + + + + + + + + + + + + 0 + 0 + 472 + 30 + + + + + + + + diff --git a/alexandrov_dmitrii_lab_4/publisher_app/publisher_app.pro b/alexandrov_dmitrii_lab_4/publisher_app/publisher_app.pro new file mode 100644 index 0000000..92e2293 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/publisher_app/publisher_app.pro @@ -0,0 +1,43 @@ +QT += core gui widgets + +CONFIG += c++11 + +LIBS += -L/usr/lib -lamqpcpp -L/usr/lib/x86_64-linux-gnu/ -levent -lpthread -ldl + +SOURCES += \ + clientRBcpp/client.cpp \ + clientRBcpp/clientrbcpp.cpp \ + clientRBcpp/cworker.cpp \ + clientRBcpp/headers.cpp \ + clientRBcpp/producemessage.cpp \ + clientRBcpp/properties.cpp \ + clientRBcpp/pworker.cpp \ + clientRBcpp/receiver.cpp \ + clientRBcpp/sender.cpp \ + clientRBcpp/validator.cpp \ + clientRBcpp/vworker.cpp \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + clientRBcpp/_client_.h \ + clientRBcpp/client_cpp.h \ + clientRBcpp/clientrbcpp.h \ + clientRBcpp/clientrbcpp_global.h \ + clientRBcpp/cworker.h \ + clientRBcpp/headers.h \ + clientRBcpp/options/clientoption.h \ + clientRBcpp/options/consumeoption.h \ + clientRBcpp/options/exchangeoption.h \ + clientRBcpp/options/queueoption.h \ + clientRBcpp/producemessage.h \ + clientRBcpp/properties.h \ + clientRBcpp/pworker.h \ + clientRBcpp/receiver.h \ + clientRBcpp/sender.h \ + clientRBcpp/validator.h \ + clientRBcpp/vworker.h \ + mainwindow.h + +FORMS += \ + mainwindow.ui diff --git a/alexandrov_dmitrii_lab_4/publisher_service b/alexandrov_dmitrii_lab_4/publisher_service new file mode 100755 index 0000000..b7f7d14 Binary files /dev/null and b/alexandrov_dmitrii_lab_4/publisher_service differ diff --git a/alexandrov_dmitrii_lab_4/readme.md b/alexandrov_dmitrii_lab_4/readme.md new file mode 100644 index 0000000..7fc1280 --- /dev/null +++ b/alexandrov_dmitrii_lab_4/readme.md @@ -0,0 +1,26 @@ +## Задание + +С помощью брокера сообщений RabbitMQ создать сервисы для демонстрации работы асинхронного обмена. + +## Выполнение + +В ходе работы над дипломной и курсовой работами RabbitMQ был уже освоен в начале года. Для лабораторной использовались наработки, оставшиеся с курсовой прошлого года (библиотки amqpcpp и модифицированные исходники clientRBcpp). + +Для демонстрации были созданы три программы, одна - отправляющая сообщения и две - принимающий. Они выводят листинг посылки/приёма сообщений на формы для отображения. Одна принимающая программа (consumer_slow) во время обработки 3 секунды бездействует, не потребляя сообщения мгновенно. + +## Результат + +Запущенные программы (по одному экземпляру): +![Программы по одному](screens/serv_1.png) + +Запущенные посыльщик и несколько медленных потребителя: +![Программы медленные](screens/serv_slow.png) + +Точка обмена на сервере: +![Точка обмена](screens/exchange.png) + +Очереди на сервере: +![Очереди](screens/queues.png) + +## Ссылка на видео +https://drive.google.com/file/d/1aYRajmeNp0omxsq9CzkhiuEyWa3-tUYz/view?usp=sharing \ No newline at end of file diff --git a/alexandrov_dmitrii_lab_4/screens/exchange.png b/alexandrov_dmitrii_lab_4/screens/exchange.png new file mode 100644 index 0000000..4110fd9 Binary files /dev/null and b/alexandrov_dmitrii_lab_4/screens/exchange.png differ diff --git a/alexandrov_dmitrii_lab_4/screens/queues.png b/alexandrov_dmitrii_lab_4/screens/queues.png new file mode 100644 index 0000000..0304138 Binary files /dev/null and b/alexandrov_dmitrii_lab_4/screens/queues.png differ diff --git a/alexandrov_dmitrii_lab_4/screens/serv_1.png b/alexandrov_dmitrii_lab_4/screens/serv_1.png new file mode 100644 index 0000000..d40fe7a Binary files /dev/null and b/alexandrov_dmitrii_lab_4/screens/serv_1.png differ diff --git a/alexandrov_dmitrii_lab_4/screens/serv_slow.png b/alexandrov_dmitrii_lab_4/screens/serv_slow.png new file mode 100644 index 0000000..7de7ee5 Binary files /dev/null and b/alexandrov_dmitrii_lab_4/screens/serv_slow.png differ