initial
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user