#include "EchoServer.h" #include #include // THE CONSTRUCTOR (Birth) // We just initialize our variables. We haven't built any network connections yet. EchoServer::EchoServer(const int p) : serverSocket(INVALID_SOCKET), port(p) {} // THE DESTRUCTOR (Cleanup) // When the Server object is destroyed, we must hang up the phone. EchoServer::~EchoServer() { if (serverSocket != INVALID_SOCKET) { CLOSE_SOCKET(serverSocket); } } // THE START METHOD (The Main Algorithm) [[noreturn]] void EchoServer::start() { // ========================================================================= // STEP 1: CREATE THE SOCKET (Building the physical telephone) // ========================================================================= // AF_INET = We are using IPv4 addresses. // SOCK_STREAM = We want a reliable, two-way stream (TCP), not just random UDP packets. // 0 = Let the OS pick the default protocol (which is TCP for SOCK_STREAM). // Success: We now have a "telephone", but it's not plugged in yet. serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == INVALID_SOCKET) { throw std::runtime_error("Failed to create the socket!"); } // Allow the port to be reused immediately if we restart the program. // Without this, if you crash the server and restart it, the OS will say // "Port 8080 is still in use!" for about 30 seconds. This line prevents that. constexpr int opt = 1; setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)); // ========================================================================= // STEP 2: BIND (Tuning the telephone to a specific channel/number) // ========================================================================= // Right now, our telephone has no phone number. We need to assign it an // IP address and a Port number so clients know where to call us. sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; // IPv4 serverAddr.sin_addr.s_addr = INADDR_ANY; // Listen on ALL Wi-Fi/Ethernet cards on this PC serverAddr.sin_port = htons(port); // Set the port (e.g., 8080). // htons = "Host to Network Short" (converts computer math to network math) // Bind the physical socket to this specific address and port. if (bind(serverSocket, reinterpret_cast(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) { throw std::runtime_error("Failed to bind to port " + std::to_string(port) + "!"); } // ========================================================================= // STEP 3: LISTEN (Turning on the ringer) // ========================================================================= // The phone is built and has a number. Now we tell the Operating System: // "Please start listening for incoming calls on this line." // SOMAXCONN tells the OS to make the "waiting line" for callers as big as possible. if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) { throw std::runtime_error("Failed to listen!"); } std::cout << "🟢 Server is listening on port " << port << "...\n"; // ========================================================================= // STEP 4: THE INFINITE LOOP (Waiting for calls forever) // ========================================================================= while (true) { sockaddr_in clientAddr{}; socklen_t clientLen = sizeof(clientAddr); // ===================================================================== // STEP 5: ACCEPT (Picking up the ringing phone) // ===================================================================== // This function BLOCKS the code until someone actually calls. // MAGIC TRICK: When a client connects, the OS creates a BRAND NEW SOCKET // (clientSocket) just for talking to them. // Our original 'serverSocket' stays free to go back to the top of the loop // and wait for the NEXT caller. This is how we handle multiple people! if (SOCKET_TYPE clientSocket = accept(serverSocket, reinterpret_cast(&clientAddr), &clientLen); clientSocket != INVALID_SOCKET) { std::cout << "📞 New client connected!\n"; // Spawn a new thread (a clone of ourselves) to talk to this specific client. // Because it's a new thread, the main loop instantly goes back to accept() // the next caller. This is the "Async" magic! std::thread([clientSocket]() { handleClient(clientSocket); }).detach(); } } } // 4. THE HELPER METHOD (The actual conversation with one client) void EchoServer::handleClient(const SOCKET_TYPE clientSocket) { char buffer[1025]; // A box to hold the incoming words while (true) { // ===================================================================== // STEP 6: RECV (Listening to the microphone) // ===================================================================== // We wait for the client to speak. // We read up to 1024 bytes (sizeof(buffer) - 1) into our 'buffer' box. // We leave 1 byte empty at the end so we can add the '\0' stop-sign later. const auto bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); // If bytesReceived is 0, the client hung up politely. // If it's < 0, the connection broke or errored out. if (bytesReceived <= 0) { std::cout << "📴 Client disconnected.\n"; break; // Exit the while loop to close the socket. } // Convert the byte count to a safe size for array indexing const auto len = static_cast(bytesReceived); // Add the invisible C-string stop-sign so we can print it safely buffer[len] = '\0'; std::cout << "📩 Received: " << buffer; // ===================================================================== // STEP 7: SEND (Speaking into the microphone) // ===================================================================== // We take the exact same words we just heard, and send them back! // This is why it's called an "Echo" server. send(clientSocket, buffer, static_cast(len), 0); } // ===================================================================== // STEP 8: CLOSE (Hanging up the phone) // ===================================================================== // The client disconnected, so we are done talking to them. // We close their specific 'clientSocket'. // The main 'serverSocket' is still open and listening for others! CLOSE_SOCKET(clientSocket); }