#include "ServerSocketThread.h" #include #include // THE CONSTRUCTOR ServerSocketThread::ServerSocketThread(const int p) : serverSocket(INVALID_SOCKET), port(p) {} // THE DESTRUCTOR // If the object is destroyed, we MUST ensure the background thread is stopped first! ServerSocketThread::~ServerSocketThread() { stop(); } // THE START (Spawning the Worker) void ServerSocketThread::start() { if (running.load()) { std::cerr << "āš ļø Server is already running!\n"; return; } // ========================================================================= // STEP 1: CREATE, BIND, LISTEN (Same as before, but in the main thread) // ========================================================================= serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == INVALID_SOCKET) { throw std::runtime_error("Failed to create socket!"); } constexpr int opt = 1; setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)); sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(serverSocket, reinterpret_cast(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) { throw std::runtime_error("Failed to bind to port " + std::to_string(port) + "!"); } if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) { throw std::runtime_error("Failed to listen!"); } // ========================================================================= // STEP 2: RAISE THE "GO" FLAG AND SPAWN THE THREAD // ========================================================================= running.store(true); // We pass 'this' so the background thread can access the serverLoop method serverThread = std::thread(&ServerSocketThread::serverLoop, this); std::cout << "🟢 Server thread started and listening on port " << port << "...\n"; } // THE STOP METHOD (The Interrupt Command) void ServerSocketThread::stop() { if (!running.load()) { std::cerr << "āš ļø Server is already stopped!\n"; return; } std::cout << "\nšŸ›‘ Interrupt received. Shutting down server...\n"; // Lower the "Go" flag. The background loop will see this on its next check. running.store(false); // FORCE WAKEUP TRICK: Closing the socket from THIS thread will immediately // cause the 'select' or 'accept' call in the OTHER thread to fail and return. // This guarantees we don't have to wait for the 1-second timeout to finish! if (serverSocket != INVALID_SOCKET) { CLOSE_SOCKET(serverSocket); serverSocket = INVALID_SOCKET; } // Wait for the background thread to finish its cleanup and join us. if (serverThread.joinable()) { serverThread.join(); } std::cout << "āœ… Server thread stopped cleanly.\n"; } // THE SERVER LOOP (The Background Algorithm) void ServerSocketThread::serverLoop() const { std::cout << "🟢 Server loop is now running in the background.\n"; // We loop AS LONG AS the stop sign is NOT raised. while (running.load()) { // ===================================================================== // STEP 3: THE ALARM CLOCK (select with timeout) // ===================================================================== // Instead of blocking forever on accept(), we ask the OS: // "Is there a client trying to connect? I'll wait up to 1 second." fd_set readfds; FD_ZERO(&readfds); FD_SET(serverSocket, &readfds); timeval tv{}; tv.tv_sec = 1; // 1 second timeout tv.tv_usec = 0; // select() returns: // > 0 : The socket is ready to be accepted // 0 : Timeout (1 second passed, no clients) // < 0 : Error (or the socket was closed by our stop() method) #ifdef _WIN32 const int activity = select(0, &readfds, nullptr, nullptr, &tv); // Windows ignores the first param #else // POSIX needs max_fd + 1 const int activity = select(static_cast(serverSocket + 1), &readfds, nullptr, nullptr, &tv); #endif // CRITICAL CHECK: Did someone call stop() while we were sleeping? if (!running.load()) { break; // Exit the loop immediately } if (activity > 0 && FD_ISSET(serverSocket, &readfds)) { // ================================================================= // STEP 4: ACCEPT (A client is here) // ================================================================= sockaddr_in clientAddr{}; socklen_t clientLen = sizeof(clientAddr); if (SOCKET_TYPE clientSocket = accept(serverSocket, reinterpret_cast(&clientAddr), &clientLen); clientSocket != INVALID_SOCKET) { std::cout << "šŸ“ž New client connected! Spawning handler thread...\n"; // Spawn a detached thread to handle this specific client std::thread([clientSocket]() { handleClient(clientSocket); }).detach(); } } else if (activity < 0) { // This usually happens when stop() closes the socket, forcing select to return an error. // We just break the loop to shut down gracefully. std::cout << "Usually happens when stop() closes the socket\n"; break; } // If activity == 0, it's just a timeout. The while loop repeats, // checks isRunning again, and sets the alarm clock for another second. } } // 6. THE HELPER METHOD (Unchanged, perfectly safe echo logic) void ServerSocketThread::handleClient(SOCKET_TYPE clientSocket) { char buffer[1025]; while (true) { auto bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); if (bytesReceived <= 0) { std::cout << "šŸ““ Client disconnected.\n"; break; } const auto len = static_cast(bytesReceived); buffer[len] = '\0'; std::cout << "šŸ“© Received: " << buffer; send(clientSocket, buffer, static_cast(len), 0); } CLOSE_SOCKET(clientSocket); }