diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ed0914..4c0fe13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,32 +7,57 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) +set(HEADERS + MainWindow.h + homeButton.h + Homologation.h + InGame.h + TeamChooser.h + TestMode.h + TestModeBtn.h + preparation/TiretteState.h + preparation/Lidar.h + PreparationMatch.h + preparation/OneItemPreparation.h +) + +set(SOURCES + main.cpp + MainWindow.cpp + homeButton.cpp + Homologation.cpp + InGame.cpp + TeamChooser.cpp + TestMode.cpp + TestModeBtn.cpp + PreparationMatch.cpp + preparation/Lidar.cpp + preparation/OneItemPreparation.cpp + preparation/TiretteState.cpp +) + +set(TCP + tcp/TCPServer.hpp + tcp/TCPServer.cpp + tcp/utils.hpp + tcp/QTTCPSocketServer.cpp + tcp/QTTCPSocketServer.h +) + + find_package(Qt6 COMPONENTS Core Gui Widgets + Network REQUIRED) -add_executable(ihm_robot main.cpp resource.qrc - MainWindow.cpp - MainWindow.h - homeButton.cpp - homeButton.h - Homologation.cpp - Homologation.h - InGame.cpp - InGame.h - TeamChooser.cpp - TeamChooser.h - TestMode.cpp - TestMode.h - TestModeBtn.cpp - TestModeBtn.h -) +add_executable(ihm_robot resource.qrc ${HEADERS} ${SOURCES} ${TCP}) target_link_libraries(ihm_robot Qt::Core Qt::Gui Qt::Widgets + Qt::Network ) diff --git a/MainWindow.h b/MainWindow.h index 08d871b..3481921 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -1,12 +1,14 @@ #pragma once + #include #include #include -#include +#include #include "homeButton.h" #include "Homologation.h" #include "InGame.h" +#include "PreparationMatch.h" #include "TeamChooser.h" #include "TestMode.h" @@ -58,9 +60,12 @@ public: connect(this->homologation, &Homologation::replierClicked, this, &MainWindow::replierRobot); this->teamChooser = new TeamChooser(centralWidget); - connect(this->teamChooser, &TeamChooser::blueTeamClicked, this, &MainWindow::onInGamePressed); + connect(this->teamChooser, &TeamChooser::spawnPointChoose, this, &MainWindow::onSpawnPointChoose); - connect(this->teamChooser, &TeamChooser::yellowTeamClicked, this, &MainWindow::onInGamePressed); + this->preparationMatch = new PreparationMatch(centralWidget); + connect(this->preparationMatch, &PreparationMatch::startGame, this, &MainWindow::onStartGame); + // connect(this->preparationMatch, &PreparationMatch::askTCPServer, this, &MainWindow::broadcastTCPMessage); + connect(this->preparationMatch, &PreparationMatch::askTCPServer, this, &MainWindow::broadcastTCPMessage); this->testMode = new TestMode(centralWidget); connect(this->testMode, &TestMode::goPressed, this, &MainWindow::moveRobot); @@ -71,6 +76,7 @@ public: this->stackedWidget->addWidget(this->home); this->stackedWidget->addWidget(this->homologation); this->stackedWidget->addWidget(this->teamChooser); + this->stackedWidget->addWidget(this->preparationMatch); this->stackedWidget->addWidget(this->testMode); this->stackedWidget->addWidget(this->inGame); @@ -86,11 +92,15 @@ public: QPalette palette; palette.setBrush(this->backgroundRole(), QBrush(QPixmap(":/img/table.jpg", "JPG").scaled(this->size(), Qt::IgnoreAspectRatio))); this->setPalette(palette); + this->homeBtn->hide(); + this->quit->hide(); } else { QPalette palette; palette.setBrush(this->backgroundRole(), QBrush(Qt::white)); this->setPalette(palette); + this->homeBtn->show(); + this->quit->show(); } this->stackedWidget->setCurrentIndex(index); } @@ -112,13 +122,18 @@ protected slots: } void onTestModePressed() + { + this->setWidgetNb(4); + } + + void onSpawnPointChoose(int nb) { this->setWidgetNb(3); } - void onInGamePressed() + void onStartGame() { - this->setWidgetNb(4); + this->setWidgetNb(5); } void onDeplierRobot() @@ -131,10 +146,24 @@ protected slots: emit replierRobot(); } +public slots: + void onTCPMesssageReceived(const std::string& message) + { + QString qMessage = QString::fromStdString(message); + + auto list = qMessage.split(";"); + + if (list[2].startsWith("pong")) + { + preparationMatch->responseFromPing(qMessage); + } + } + signals: void deplierRobot(); void replierRobot(); void moveRobot(int x, int y, int theta); + void broadcastTCPMessage(const std::string& message); private: QVBoxLayout* mainLayout; @@ -147,6 +176,7 @@ private: homeButton* home; Homologation* homologation; TeamChooser* teamChooser; + PreparationMatch* preparationMatch; TestMode* testMode; InGame* inGame; }; diff --git a/PreparationMatch.cpp b/PreparationMatch.cpp new file mode 100644 index 0000000..543f807 --- /dev/null +++ b/PreparationMatch.cpp @@ -0,0 +1,5 @@ +// +// Created by acki on 3/27/24. +// + +#include "PreparationMatch.h" diff --git a/PreparationMatch.h b/PreparationMatch.h new file mode 100644 index 0000000..4afffea --- /dev/null +++ b/PreparationMatch.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include + +#include "preparation/Lidar.h" +#include "preparation/OneItemPreparation.h" +#include "preparation/TiretteState.h" + +class PreparationMatch : public QWidget { + Q_OBJECT +public: + PreparationMatch(QWidget* parent = nullptr) : QWidget(parent) + { + this->mainLayout = new QVBoxLayout(this); + this->gridLayout = new QHBoxLayout(); + + this->startButton = new QPushButton("Play", this); + + this->leftLayout = new QVBoxLayout(); + this->lidar = new Lidar(this); + this->leftLayout->addWidget(lidar); + + this->tiretteState = new TiretteState(this); + this->leftLayout->addWidget(tiretteState); + + this->rightLayout = new QVBoxLayout(); + this->ledVerte = new OneItemPreparation("Led verte", "Check", this); + connect(this->ledVerte, &OneItemPreparation::buttonClicked, this, [=]() { + this->ledVerte->toggleChecked(); + }); + + this->arduino = new OneItemPreparation("Arduino", "Ping", this); + connect(this->arduino, &OneItemPreparation::buttonClicked, this, [=]() { + // emit askTCPServer("start;tirette;ping;0"); + + this->arduino->toggleChecked(); + }); + + this->aruco = new OneItemPreparation("Aruco", "Ping", this); + connect(this->aruco, &OneItemPreparation::buttonClicked, this, [=]() { + emit askTCPServer("strat;aruco;ping;0"); + }); + + this->lidarPing = new OneItemPreparation("Lidar", "Ping", this); + connect(this->lidarPing, &OneItemPreparation::buttonClicked, this, [=]() { + emit askTCPServer("strat;lidar;ping;0"); + }); + + this->tirette = new OneItemPreparation("Tirette", "Ping", this); + connect(this->tirette, &OneItemPreparation::buttonClicked, this, [=]() { + emit askTCPServer("strat;tirette;ping;0"); + }); + + this->rightLayout->addWidget(ledVerte); + this->rightLayout->addWidget(arduino); + this->rightLayout->addWidget(aruco); + this->rightLayout->addWidget(lidarPing); + this->rightLayout->addWidget(tirette); + + this->gridLayout->addLayout(leftLayout); + this->gridLayout->addLayout(rightLayout); + + this->mainLayout->addLayout(gridLayout); + this->mainLayout->addWidget(startButton); + + connect(this->startButton, &QPushButton::pressed, this, &PreparationMatch::onStartButtonClicked); + } + + void responseFromPing(const QString& message) + { + auto list = message.split(";"); + + if (list[0] == "tirette") { + this->tirette->setChecked(true); + } else if (list[0] == "lidar") { + this->lidarPing->setChecked(true); + } else if (list[0] == "aruco") { + this->aruco->setChecked(true); + } + // TODO check how we ping the arduino + /*else if (list[0] == "arduino") { + this->arduino->setChecked(true); + }*/ + } + +signals: + void startGame(); + + void askTCPServer(const std::string& message); + +public slots: + void onStartButtonClicked() + { + emit startGame(); + } + + +private: + QVBoxLayout* mainLayout; + QHBoxLayout* gridLayout; + QVBoxLayout* leftLayout; + QVBoxLayout* rightLayout; + + Lidar* lidar; + OneItemPreparation* ledVerte; + OneItemPreparation* arduino; + OneItemPreparation* aruco; + OneItemPreparation* lidarPing; + OneItemPreparation* tirette; + TiretteState* tiretteState; + + QPushButton* startButton; +}; diff --git a/TeamChooser.h b/TeamChooser.h index 6d2b06f..8c28f7c 100644 --- a/TeamChooser.h +++ b/TeamChooser.h @@ -8,36 +8,90 @@ class TeamChooser : public QWidget { public: TeamChooser(QWidget* parent = nullptr) : QWidget(parent) { - this->mainLayout = new QHBoxLayout(this); - this->blueTeam = new QPushButton("Equipe Bleue", this); - this->blueTeam->setStyleSheet("background-color: #4D83A1; border-radius: 40px; margin-top: 20px; color: black;"); - this->yellowTeam = new QPushButton("Equipe Jaune", this); - this->yellowTeam->setStyleSheet("background-color: #FFBF00; border-radius: 40px; margin-top: 20px; color: black;"); + // set a border + // this->setStyleSheet("border: 10px solid black;"); - this->mainLayout->addWidget(this->blueTeam); - this->mainLayout->addWidget(this->yellowTeam); + this->mainLayout = new QVBoxLayout(this); - connect(this->blueTeam, &QPushButton::pressed, this, &TeamChooser::onBlueTeamClicked); - connect(this->yellowTeam, &QPushButton::pressed, this, &TeamChooser::onYellowTeamClicked); + this->topLayout = new QHBoxLayout(); + this->middleLayout = new QHBoxLayout(); + this->bottomLayout = new QHBoxLayout(); + + this->spawnPoint1 = new QPushButton("1", this); + this->spawnPoint1->setFixedSize(50, 50); + this->spawnPoint1->setStyleSheet("border: 1px solid black; color: white; background-color: rgba(0, 0, 255, 200);"); + this->spawnPoint2 = new QPushButton("2", this); + this->spawnPoint2->setFixedSize(50, 50); + this->spawnPoint2->setStyleSheet("border: 1px solid black; color: black; background-color: rgba(255, 255, 0, 200);"); + this->spawnPoint3 = new QPushButton("3", this); + this->spawnPoint3->setFixedSize(50, 50); + this->spawnPoint3->setStyleSheet("border: 1px solid black; color: white; background-color: rgba(0, 0, 255, 200);"); + this->spawnPoint4 = new QPushButton("4", this); + this->spawnPoint4->setFixedSize(50, 50); + this->spawnPoint4->setStyleSheet("border: 1px solid black; color: black; background-color: rgba(255, 255, 0, 200);"); + this->spawnPoint5 = new QPushButton("5", this); + this->spawnPoint5->setFixedSize(50, 50); + this->spawnPoint5->setStyleSheet("border: 1px solid black; color: white; background-color: rgba(0, 0, 255, 200);"); + this->spawnPoint6 = new QPushButton("6", this); + this->spawnPoint6->setFixedSize(50, 50); + this->spawnPoint6->setStyleSheet("border: 1px solid black; color: black; background-color: rgba(255, 255, 0, 200);"); + + this->mainLayout->addLayout(topLayout); + this->mainLayout->addLayout(middleLayout); + this->mainLayout->addLayout(bottomLayout); + + this->topLayout->addWidget(spawnPoint1, 0, Qt::AlignTop | Qt::AlignLeft); + this->middleLayout->addWidget(spawnPoint2, 0, Qt::AlignCenter | Qt::AlignLeft); + this->bottomLayout->addWidget(spawnPoint3, 0, Qt::AlignBottom | Qt::AlignLeft); + this->topLayout->addWidget(spawnPoint4, 0, Qt::AlignTop | Qt::AlignRight); + this->middleLayout->addWidget(spawnPoint5, 0, Qt::AlignCenter | Qt::AlignRight); + this->bottomLayout->addWidget(spawnPoint6, 0, Qt::AlignBottom | Qt::AlignRight); + + connect(this->spawnPoint1, &QPushButton::pressed, this, [=]() { + spawnPointClicked(1); + }); + + connect(this->spawnPoint2, &QPushButton::pressed, this, [=]() { + spawnPointClicked(2); + }); + + connect(this->spawnPoint3, &QPushButton::pressed, this, [=]() { + spawnPointClicked(3); + }); + + connect(this->spawnPoint4, &QPushButton::pressed, this, [=]() { + spawnPointClicked(4); + }); + + connect(this->spawnPoint5, &QPushButton::pressed, this, [=]() { + spawnPointClicked(5); + }); + + connect(this->spawnPoint6, &QPushButton::pressed, this, [=]() { + spawnPointClicked(6); + }); } signals: - void blueTeamClicked(); - void yellowTeamClicked(); + void spawnPointChoose(int nb); private slots: - void onBlueTeamClicked() + void spawnPointClicked(int nb) { - emit blueTeamClicked(); - } - - void onYellowTeamClicked() - { - emit yellowTeamClicked(); + emit spawnPointChoose(nb); } private: - QHBoxLayout* mainLayout; - QPushButton* blueTeam; - QPushButton* yellowTeam; + QVBoxLayout* mainLayout; + QHBoxLayout* topLayout; + QHBoxLayout* middleLayout; + QHBoxLayout* bottomLayout; + + QPushButton* spawnPoint1; + QPushButton* spawnPoint2; + QPushButton* spawnPoint3; + QPushButton* spawnPoint4; + QPushButton* spawnPoint5; + QPushButton* spawnPoint6; + }; diff --git a/main.cpp b/main.cpp index cc04ccd..56a7d3a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,10 @@ +#include #include #include +#include #include "MainWindow.h" +#include "tcp/TCPServer.hpp" int main(int argc, char* argv[]) { QApplication a(argc, argv); @@ -23,9 +26,50 @@ int main(int argc, char* argv[]) { qInfo() << "move" << x << y << theta; }); - //main->show(); + main->show(); + // main->showFullScreen(); - main->showFullScreen(); + auto* server = new TCPServer(8082); + server->start(); + + QObject::connect(server, &TCPServer::messageReceived, main, &MainWindow::onTCPMesssageReceived); + QObject::connect(main, &MainWindow::broadcastTCPMessage, [server](const std::string& message) + { + server->broadcastMessage(message.c_str()); + }); + + // Create a new thread for the server + QThread serverThread; + server->moveToThread(&serverThread); + serverThread.start(); + + // Create a lambda function to run the while loop + auto runServerLoop = [&]() { + while (true) { + std::string message; + std::cout << "Enter message ('quit' to exit): "; + std::getline(std::cin, message); + + if (message == "quit") { + // Stop the server and exit the loop + server->stop(); + break; + } + + // Broadcast the message from the server + server->broadcastMessage(message.c_str()); + } + }; + + // Move the lambda function to the new thread + QObject::connect(&serverThread, &QThread::started, runServerLoop); + + // Connect aboutToQuit signal to stop the thread and wait for it to finish + QObject::connect(&a, &QCoreApplication::aboutToQuit, [&]() { + server->stop(); // Stop the server + serverThread.quit(); // Quit the thread event loop + serverThread.wait(); // Wait for the thread to finish + }); return QApplication::exec(); } diff --git a/preparation/Lidar.cpp b/preparation/Lidar.cpp new file mode 100644 index 0000000..6c663ba --- /dev/null +++ b/preparation/Lidar.cpp @@ -0,0 +1,5 @@ +// +// Created by acki on 3/27/24. +// + +#include "Lidar.h" diff --git a/preparation/Lidar.h b/preparation/Lidar.h new file mode 100644 index 0000000..2f83e53 --- /dev/null +++ b/preparation/Lidar.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include +#include + +class Lidar : public QWidget { + Q_OBJECT + +public: + Lidar(QWidget* parent = nullptr) : QWidget(parent) + { + this->mainLayout = new QVBoxLayout(this); + this->mainLayout->setAlignment(Qt::AlignTop); + this->title = new QLabel("Lidar", this); + this->title->setStyleSheet("font-size: 24px; color: black;"); + + this->position = new QHBoxLayout(); + this->postionTitle = new QLabel("x: 0, y : 0, r: 0", this); + this->positionButton = new QPushButton("Get pos", this); + + this->position->addWidget(postionTitle); + this->position->addWidget(positionButton); + + this->health = new QHBoxLayout(); + this->healthTitle = new QLabel("Health : OK", this); + this->healthButton = new QPushButton("Get health", this); + + this->health->addWidget(healthTitle); + this->health->addWidget(healthButton); + + this->mainLayout->addWidget(title, 0, Qt::AlignCenter); + this->mainLayout->addLayout(position); + this->mainLayout->addLayout(health); + } + +private: + QVBoxLayout* mainLayout; + QLabel* title; + QHBoxLayout* position; + QHBoxLayout* health; + + QLabel* postionTitle; + QPushButton* positionButton; + + QLabel* healthTitle; + QPushButton* healthButton; +}; diff --git a/preparation/OneItemPreparation.cpp b/preparation/OneItemPreparation.cpp new file mode 100644 index 0000000..ac72d9b --- /dev/null +++ b/preparation/OneItemPreparation.cpp @@ -0,0 +1,5 @@ +// +// Created by acki on 3/27/24. +// + +#include "OneItemPreparation.h" diff --git a/preparation/OneItemPreparation.h b/preparation/OneItemPreparation.h new file mode 100644 index 0000000..d276b89 --- /dev/null +++ b/preparation/OneItemPreparation.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include + +class OneItemPreparation : public QWidget { + Q_OBJECT + +public: + OneItemPreparation(const QString& title, const QString& buttonText, QWidget* parent = nullptr) : QWidget(parent) + { + this->mainLayout = new QHBoxLayout(this); + + this->item = new QLabel(title, this); + this->checkBox = new QCheckBox(this); + this->button = new QPushButton(buttonText, this); + + this->mainLayout->addWidget(item); + this->mainLayout->addWidget(checkBox); + this->mainLayout->addWidget(button); + + this->checkBox->setDisabled(true); + + connect(this->button, &QPushButton::pressed, this, &OneItemPreparation::onButtonClicked); + } + + void setChecked(const bool checked) const + { + this->checkBox->setChecked(checked); + } + + void toggleChecked() const + { + this->checkBox->toggle(); + } + +signals: + void buttonClicked(); + +public slots: + void onButtonClicked() + { + emit buttonClicked(); + } + + +private: + QHBoxLayout* mainLayout; + QLabel* item; + QCheckBox* checkBox; + QPushButton* button; +}; diff --git a/preparation/TiretteState.cpp b/preparation/TiretteState.cpp new file mode 100644 index 0000000..ab66959 --- /dev/null +++ b/preparation/TiretteState.cpp @@ -0,0 +1,5 @@ +// +// Created by acki on 3/27/24. +// + +#include "TiretteState.h" diff --git a/preparation/TiretteState.h b/preparation/TiretteState.h new file mode 100644 index 0000000..4f84e8d --- /dev/null +++ b/preparation/TiretteState.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include + +class TiretteState : public QWidget { + Q_OBJECT + +public: + TiretteState(QWidget* parent = nullptr) : QWidget(parent) + { + this->mainLayout = new QVBoxLayout(this); + this->title = new QLabel("Tirette", this); + this->title->setStyleSheet("font-size: 24px; color: black;"); + + this->tiretteStateLayout = new QHBoxLayout(); + this->stateLabel = new QLabel("1 : ", this); + this->stateButton = new QPushButton("Get state", this); + this->tiretteStateLayout->addWidget(stateLabel); + this->tiretteStateLayout->addWidget(stateButton); + + this->mainLayout->addWidget(this->title, 0, Qt::AlignCenter); + this->mainLayout->addLayout(this->tiretteStateLayout); + } + +private: + QVBoxLayout* mainLayout; + QLabel* title; + QHBoxLayout* tiretteStateLayout; + QLabel* stateLabel; + QPushButton* stateButton; +}; diff --git a/tcp/CMakeLists.txt b/tcp/CMakeLists.txt new file mode 100644 index 0000000..8aa99ba --- /dev/null +++ b/tcp/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.25) +project(socketServer) + +set(CMAKE_CXX_STANDARD 17) + +add_executable(socketServer main.cpp + TCPServer.cpp + TCPServer.hpp + utils.hpp) diff --git a/tcp/TCPServer.cpp b/tcp/TCPServer.cpp new file mode 100644 index 0000000..094de89 --- /dev/null +++ b/tcp/TCPServer.cpp @@ -0,0 +1,149 @@ +#include "TCPServer.hpp" + +#include "utils.hpp" + +ClientHandler::ClientHandler(int clientSocket, TCPServer* server) : clientSocket(clientSocket), server(server) {}; + +void ClientHandler::handle() { + std::string buffer; + buffer.reserve(4096); // Pre-allocate memory to avoid frequent allocations + + while (true) { + char tempBuffer[4096] = {0}; + ssize_t valread = recv(clientSocket, tempBuffer, sizeof(tempBuffer), 0); + + if (valread > 0) { + buffer.append(tempBuffer, valread); + //std::cout << "Received: " << buffer << std::endl; + + if (buffer == "quit") { + std::cout << "Client requested to quit. Closing connection." << std::endl; + break; + } + processMessage(buffer); + + buffer.clear(); + } else if (valread == 0) { + std::cout << "Client disconnected." << std::endl; + break; // Client disconnected + } else { + std::cerr << "Failed to receive data." << std::endl; + break; // Error in receiving data + } + } + + closeConnection(); +} + +void ClientHandler::processMessage(const std::string& message) { + server->handleMessage(message, clientSocket); +} + +void ClientHandler::closeConnection() { + close(clientSocket); + server->clientDisconnected(clientSocket); // Inform the server that the client has disconnected +} + +TCPServer::TCPServer(int port, QObject* parent) : QObject(parent) +{ + serverSocket = socket(AF_INET, SOCK_STREAM, 0); + if (serverSocket == -1) { + std::cerr << "Socket creation failed" << std::endl; + exit(EXIT_FAILURE); + } + + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(port); + + if (bind(serverSocket, reinterpret_cast(&address), sizeof(address)) == -1) { + std::cerr << "Binding failed" << std::endl; + exit(EXIT_FAILURE); + } + + if (listen(serverSocket, 5) == -1) { + std::cerr << "Listening failed" << std::endl; + exit(EXIT_FAILURE); + } + + std::cout << "Server started on port " << port << std::endl; +} + +void TCPServer::acceptConnections() +{ + while (!shouldStop) { + sockaddr_in clientAddress{}; + int addrlen = sizeof(clientAddress); + int clientSocket = + accept(serverSocket, reinterpret_cast(&clientAddress), reinterpret_cast(&addrlen)); + if (clientSocket == -1) { + std::cerr << "Accepting connection failed" << std::endl; + continue; + } + std::cout << "Connection accepted" << std::endl; + + // Add the client socket to the list + clientSockets.push_back(clientSocket); + connectedClients++; + + // Handle client connection in a separate thread + clientThreads.emplace_back(&ClientHandler::handle, ClientHandler(clientSocket, this)); + // Clean up finished threads + clientThreads.erase(std::remove_if(clientThreads.begin(), clientThreads.end(), [](std::thread &t) { + return !t.joinable(); + }), clientThreads.end()); + } +} + + + +void TCPServer::handleMessage(const std::string& message, int clientSocket) +{ + emit messageReceived(message, clientSocket); +} + + +void TCPServer::broadcastMessage(const char* message, int senderSocket) +{ + for (int clientSocket : clientSockets) { + if (clientSocket != senderSocket) { // Exclude the sender's socket + send(clientSocket, message, strlen(message), 0); + } + } +} + +void TCPServer::clientDisconnected(const int clientSocket) { + // Remove the disconnected client's socket + clientSockets.erase(std::remove(clientSockets.begin(), clientSockets.end(), clientSocket), clientSockets.end()); + // Decrement the count of connected clients + connectedClients--; +} + +void TCPServer::stop() { + shouldStop = true; + // Close all client sockets + for (int clientSocket : clientSockets) { + close(clientSocket); + } + // Close the server socket + close(serverSocket); +} + +TCPServer::~TCPServer() { + this->stop(); + // Join all threads before exiting + for (auto& thread : clientThreads) { + thread.join(); + } +} + +int TCPServer::nbClients() +{ + return connectedClients; +} + +void TCPServer::start() +{ + std::thread([this]() { acceptConnections(); }).detach(); +} diff --git a/tcp/TCPServer.hpp b/tcp/TCPServer.hpp new file mode 100644 index 0000000..7ac6833 --- /dev/null +++ b/tcp/TCPServer.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class TCPServer; // Forward declaration + +class ClientHandler { +private: + int clientSocket; + TCPServer* server; // Reference to the TCPServer instance + +public: + explicit ClientHandler(int clientSocket, TCPServer* server); + + void handle(); + + void processMessage(const std::string& message); + + void closeConnection(); +}; + +class TCPServer : public QObject { + Q_OBJECT + + int serverSocket; + std::vector clientThreads; + std::vector clientSockets; // Store connected client sockets + int connectedClients = 0; // Track the number of connected clients + bool shouldStop = false; // Flag to indicate if the server should stop + +public: + explicit TCPServer(int port, QObject* parent = nullptr); + + void start(); + + void acceptConnections(); + + // Broadcast message to all connected clients + void broadcastMessage(const char* message, int senderSocket = -1); // Modified method signature + + void handleMessage(const std::string& message, int clientSocket = -1); + + void clientDisconnected(int clientSocket); // New method to handle client disconnection + + void stop(); + + int nbClients(); + + ~TCPServer(); + +signals: + void messageReceived(const std::string& message, int clientSocket); +}; diff --git a/tcp/main.cpp b/tcp/main.cpp new file mode 100644 index 0000000..88f8dd3 --- /dev/null +++ b/tcp/main.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include "TCPServer.hpp" + +std::atomic keepRunning(true); + +void signalHandler(int signum) { + std::cout << "Interrupt signal (" << signum << ") received.\n"; + keepRunning = false; +} + +int main() { + signal(SIGTERM, signalHandler); + signal(SIGINT, signalHandler); + + TCPServer server(8080); + + try { + server.start(); + + while (keepRunning) { + sleep(1); + + server.broadcastMessage("request aruco"); + + std::cout << "Main thread communicating with server..." << std::endl; + } + + server.stop(); + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/tcp/utils.hpp b/tcp/utils.hpp new file mode 100644 index 0000000..5f19d04 --- /dev/null +++ b/tcp/utils.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +inline bool startWith(const std::string& str, const std::string& start) +{ + return str.rfind(start, 0) == 0; +} + +inline bool endsWith(const std::string& str, const std::string& end) +{ + if (str.length() >= end.length()) + { + return (0 == str.compare(str.length() - end.length(), end.length(), end)); + } + return false; +} + +inline bool contains(const std::string& str, const std::string& sub) +{ + return str.find(sub) != std::string::npos; +} + +inline std::vector split(const std::string& str, const std::string& delimiter) +{ + std::vector tokens; + size_t prev = 0, pos = 0; + do + { + pos = str.find(delimiter, prev); + if (pos == std::string::npos) pos = str.length(); + std::string token = str.substr(prev, pos - prev); + if (!token.empty()) tokens.push_back(token); + prev = pos + delimiter.length(); + } while (pos < str.length() && prev < str.length()); + return tokens; +} \ No newline at end of file