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