diff --git a/client/inc/logic/client_reactor.h b/client/inc/logic/client_reactor.h new file mode 100644 index 0000000..13b7363 --- /dev/null +++ b/client/inc/logic/client_reactor.h @@ -0,0 +1,34 @@ +#pragma once + +#include "logic/message_handler.h" +#include "messages.pb.h" +#include "services.grpc.pb.h" +#include +#include +#include +#include +#include + +class Reactor : public QObject, + public grpc::ClientBidiReactor { + Q_OBJECT +public: + explicit Reactor(chat::Chat::Stub *); + void OnWriteDone(bool ok) override; + void OnReadDone(bool ok) override; + void OnDone(const grpc::Status &s) override; + grpc::Status Await(); + void SendMessage(const chat::chatMsg &msg); + bool IsConnected() const; + std::shared_ptr getHandler(); + +private: + chat::Chat::Stub *m_stub; + grpc::ClientContext m_context; + chat::chatMsg m_msg; + std::shared_ptr m_handler; + std::mutex m_mutex; + std::condition_variable m_cv; + bool m_done = false; + grpc::Status m_status; +}; diff --git a/client/inc/logic/message_handler.h b/client/inc/logic/message_handler.h new file mode 100644 index 0000000..82999ac --- /dev/null +++ b/client/inc/logic/message_handler.h @@ -0,0 +1,14 @@ +#pragma once + +#include "messages.pb.h" +#include + +class MessageHandler : public QObject { + Q_OBJECT +public: + MessageHandler(QObject *parent = nullptr) {} + void emitMessageReceived(const chat::chatMsg &message); + +signals: + void messageReceived(const chat::chatMsg &message); +}; diff --git a/client/inc/models/message.h b/client/inc/models/message.h index 4178437..edade3f 100644 --- a/client/inc/models/message.h +++ b/client/inc/models/message.h @@ -5,13 +5,13 @@ class Message { public: - Message(int, QDateTime, QString); + Message(QString, QDateTime, QString); ~Message() {} QString toString() const; private: - int m_userId; + QString m_userId; QDateTime m_timestamp; QString m_content; }; diff --git a/client/inc/ui/mainwindow.h b/client/inc/ui/mainwindow.h index 0fb9abf..c316604 100644 --- a/client/inc/ui/mainwindow.h +++ b/client/inc/ui/mainwindow.h @@ -1,7 +1,10 @@ #pragma once +#include "logic/client_reactor.h" +#include "messages.pb.h" #include "models/chatroom.h" #include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -17,9 +20,12 @@ public: ~MainWindow(); private: + void sendMsg(const QString &); + Ui::MainWindow *ui; Chatroom m_chatroom; + std::unique_ptr m_reactor; public slots: - void receiveMsg(QString &); + void receiveMsg(const chat::chatMsg &); }; diff --git a/client/src/logic/client_reactor.cpp b/client/src/logic/client_reactor.cpp new file mode 100644 index 0000000..8fe07ca --- /dev/null +++ b/client/src/logic/client_reactor.cpp @@ -0,0 +1,61 @@ +#include "logic/client_reactor.h" +#include "messages.pb.h" +#include "services.grpc.pb.h" +#include +#include + +Reactor::Reactor(chat::Chat::Stub *stub) : m_stub(stub) { + m_stub->async()->sendMsg(&m_context, this); + + m_handler = std::make_shared(); + + StartRead(&m_msg); + StartCall(); +} + +void Reactor::OnWriteDone(bool ok) { + if (!ok) { + std::cerr << "Error: write failed" << std::endl; + } +} + +void Reactor::OnReadDone(bool ok) { + if (ok) { + m_handler->emitMessageReceived(m_msg); + std::cout << "Received message from: " << m_msg.userid() + << " content: " << m_msg.message() << std::endl; + StartRead(&m_msg); + } else { + std::cout << "Server finished sending" << std::endl; + } +} + +void Reactor::OnDone(const grpc::Status &status) { + m_status = status; + { + std::lock_guard lock(m_mutex); + m_done = true; + } + m_cv.notify_one(); + if (!status.ok()) { + std::cerr << "Error: " << status.error_message() << std::endl; + } + std::cout << "Server finished sending" << std::endl; +} + +grpc::Status Reactor::Await() { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this] { return m_done; }); + return m_status; +} + +void Reactor::SendMessage(const chat::chatMsg &msg) { + static chat::chatMsg writeMsg; + writeMsg.CopyFrom(msg); + StartWrite(&writeMsg); + std::cout << "Sent message: " << msg.message(); +} + +bool Reactor::IsConnected() const { return !m_done; } + +std::shared_ptr Reactor::getHandler() { return m_handler; } diff --git a/client/src/logic/message_handler.cpp b/client/src/logic/message_handler.cpp new file mode 100644 index 0000000..cfbdf82 --- /dev/null +++ b/client/src/logic/message_handler.cpp @@ -0,0 +1,5 @@ +#include "logic/message_handler.h" + +void MessageHandler::emitMessageReceived(const chat::chatMsg &msg) { + emit messageReceived(msg); +} diff --git a/client/src/models/message.cpp b/client/src/models/message.cpp index 7cc01c8..74dbc1b 100644 --- a/client/src/models/message.cpp +++ b/client/src/models/message.cpp @@ -1,6 +1,6 @@ #include "models/message.h" -Message::Message(int userId, QDateTime timestamp, QString content) +Message::Message(QString userId, QDateTime timestamp, QString content) : m_userId(userId), m_timestamp(timestamp), m_content(content) {} QString Message::toString() const { diff --git a/client/src/ui/mainwindow.cpp b/client/src/ui/mainwindow.cpp index 7debe25..715f909 100644 --- a/client/src/ui/mainwindow.cpp +++ b/client/src/ui/mainwindow.cpp @@ -1,20 +1,45 @@ #include "ui/mainwindow.h" +#include "logic/client_reactor.h" +#include "messages.pb.h" #include "ui_mainwindow.h" +#include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); + auto channel = grpc::CreateChannel("localhost:50051", + grpc::InsecureChannelCredentials()); + auto stub = chat::Chat::NewStub(channel); + + m_reactor = std::make_unique(stub.get()); connect(ui->sendButton, &QPushButton::clicked, this, [this]() { auto msg = ui->inputText->toPlainText(); - receiveMsg(msg); + if (!msg.isEmpty()) { + sendMsg(msg); + ui->inputText->clear(); + } }); + connect(m_reactor->getHandler().get(), &MessageHandler::messageReceived, this, + &MainWindow::receiveMsg); } MainWindow::~MainWindow() { delete ui; } -void MainWindow::receiveMsg(QString &msg) { - Message message(0, QDateTime::currentDateTime(), msg); +void MainWindow::sendMsg(const QString &msg) { + if (m_reactor && m_reactor->IsConnected()) { + chat::chatMsg chatMsg; + chatMsg.set_message(msg.toStdString()); + m_reactor->SendMessage(chatMsg); + } +} + +void MainWindow::receiveMsg(const chat::chatMsg &chatMsg) { + auto userId = QString::fromStdString(chatMsg.userid()); + auto content = QString::fromStdString(chatMsg.message()); + Message message(userId, QDateTime::currentDateTime(), content); m_chatroom.addMessage(message); ui->outputText->setText(m_chatroom.getMessagesString()); } diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index acd7d5e..27cde48 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.23) project(chat_server LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + file(GLOB_RECURSE SOURCES "src/*.ui" "src/*.cpp" "inc/*.h" "res/*.qrc") add_executable(chat_server diff --git a/server/inc/logic/service.h b/server/inc/logic/service.h index ee5bdec..6aae342 100644 --- a/server/inc/logic/service.h +++ b/server/inc/logic/service.h @@ -5,6 +5,7 @@ #include "services.grpc.pb.h" #include #include +#include class Service : public chat::Chat::CallbackService { public: @@ -14,7 +15,7 @@ public: void sendToAll(const chat::chatMsg &msg); private: - std::vector m_clients; + std::map m_clients; absl::Mutex m_mu; std::vector m_messages ABSL_GUARDED_BY(m_mu); }; diff --git a/server/src/logic/reactor.cpp b/server/src/logic/reactor.cpp index ed28b96..3b91f7e 100644 --- a/server/src/logic/reactor.cpp +++ b/server/src/logic/reactor.cpp @@ -9,6 +9,7 @@ ChatReactor::ChatReactor(Service *service, absl::Mutex *mu, void ChatReactor::OnReadDone(bool ok) { if (ok) { + std::cout << "Received message: " << m_msg.message() << std::endl; m_mu->lock(); m_messages->push_back(m_msg); m_mu->unlock(); diff --git a/server/src/logic/service.cpp b/server/src/logic/service.cpp index 8dd51cb..be21766 100644 --- a/server/src/logic/service.cpp +++ b/server/src/logic/service.cpp @@ -1,21 +1,29 @@ #include "logic/service.h" #include "logic/reactor.h" +#include +#include +#include Service::~Service() { - for (auto *client : m_clients) { - delete client; + for (auto client : m_clients) { + delete client.second; } } grpc::ServerBidiReactor * Service::sendMsg(grpc::CallbackServerContext *context) { auto newClient = new ChatReactor(this, &m_mu, &m_messages); - m_clients.push_back(newClient); + std::string newId = std::format("user_{}", std::rand()); + m_clients.insert(std::make_pair(newId, newClient)); + std::cout << "New client: " << newId << std::endl; return newClient; } void Service::sendToAll(const chat::chatMsg &msg) { - for (auto *client : m_clients) { - client->StartWrite(&msg); + std::cout << "Relay message to: "; + for (auto client : m_clients) { + std::cout << client.first << " "; + client.second->StartWrite(&msg); } + std::cout << std::endl; }