This commit is contained in:
2026-06-17 21:11:25 +03:00
commit 9a150d90ad
17 changed files with 992 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
#include "EchoServer.h"
#include <iostream>
#include <stdexcept>
// 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<sockaddr*>(&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<sockaddr*>(&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<size_t>(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<int>(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);
}