167 lines
6.2 KiB
C++
167 lines
6.2 KiB
C++
#include "ServerSocketThread.h"
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
// 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<sockaddr*>(&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<int>(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<sockaddr*>(&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<size_t>(bytesReceived);
|
|
buffer[len] = '\0';
|
|
std::cout << "📩 Received: " << buffer;
|
|
|
|
send(clientSocket, buffer, static_cast<int>(len), 0);
|
|
}
|
|
CLOSE_SOCKET(clientSocket);
|
|
} |