This commit is contained in:
2026-06-17 21:11:25 +03:00
commit 9a150d90ad
17 changed files with 992 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
#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);
}