[Webserver]miniWebserver โ€“ (2)

๐Ÿ‘‰๐Ÿป ์ด์ „ ์ฝ”๋“œ์—์„œ ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด์„œ ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
I separated the files in the previous code for readability.

๐Ÿ‘‰๐Ÿป Main.cpp์—์„œ ์„œ๋ฒ„ ์‹œ์ž‘๋ถ€๋ถ„์„ ์“ฐ๋ ˆ๋“œ๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
I separated the server startup part into a thread in Main.cpp.

๐Ÿ‘‰๐Ÿป โ—๏ธ๋กœ ํ‘œ์‹œ๋œ ๋ถ€๋ถ„์€ ๊ธฐ์กด์ฝ”๋“œ์—์„œ ๋ฐ”๋€ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
The parts marked with โ—๏ธ are the changes made to the existing code.

โœ”๏ธ Server.h

#include <string>

class Server {
public:
    // ์ƒ์„ฑ์ž: ์„œ๋ฒ„ ์ดˆ๊ธฐํ™” (IP, Port)
    // Constructor: Initialize server (IP, Port)
    Server(const std::string& ip, int port);

    // ์„œ๋ฒ„ ์†Œ์ผ“ ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ๋ฅผ ์ €์žฅํ•  ๋ฉค๋ฒ„ ๋ณ€์ˆ˜
    // Member variable to store server socket file descriptors
    int server_fd = -1; // โ—๏ธ

    // ์„œ๋ฒ„ ์‹คํ–‰ ํ•จ์ˆ˜: ๋ฆฌ์Šค๋‹์„ ์‹œ์ž‘ํ•˜๊ณ  ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ๋Œ๋ฆผ
    // Server execution function: Starts listening and runs the main loop
    bool start();

    // ์„œ๋ฒ„ ์ข…๋ฃŒ (ํ•„์š”ํ•˜๋‹ค๋ฉด)
    // Shut down the server (if necessary)
    void stop();

private:
    std::string ip_address;
    int port_number;

    // ์‹ค์ œ ๋ฆฌ์Šค๋‹ ๋ฐ ์—ฐ๊ฒฐ ์ˆ˜์‹  ๋กœ์ง (๋‚ด๋ถ€ ๊ตฌํ˜„)
    // Actual listening and connection reception logic (internal implementation)
    void run_listener();

    // ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ ๋กœ์ง (์Šค๋ ˆ๋“œ ํ’€ ๋˜๋Š” ๋ณ„๋„ ํ•ธ๋“ค๋Ÿฌ์—๊ฒŒ ์œ„์ž„)
    // Connection handling logic (delegated to thread pool or separate handler)
    void handle_client(int client_socket);
};

โœ”๏ธ Server.cpp

#include "Server.h"
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <thread>
#include <vector>

//#define PORT 5080 โ—๏ธ
#define BACKLOG 10 // ๋Œ€๊ธฐ ํ ํฌ๊ธฐ / wait queue size

Server::Server(const std::string& ip, int port)
    : ip_address(ip), port_number(port) {}

void Server::run_listener() {
    //std::cout << "[INFO] Server listening on " << ip_address << ":" << port_number << std::endl;

    // --- 1. ์†Œ์ผ“ ์ƒ์„ฑ (Socket Creation) --- //
    //int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    // ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋กœ ๋ณ€๊ฒฝ / Change to member variableโ—๏ธ
    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    //if (server_fd == 0) { โ—๏ธ
    if (server_fd == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    // --- 2. ๋ฐ”์ธ๋”ฉ (Binding) --- //
    struct sockaddr_in address;
    address.sin_family = AF_INET;         // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // ๋ชจ๋“  ์ธํ„ฐํŽ˜์ด์Šค IP ์‚ฌ์šฉ / Use all interface IPs
    address.sin_port = htons(port_number);       // ํฌํŠธ๋ฅผ ๋„คํŠธ์›Œํฌ ๋ฐ”์ดํŠธ ์ˆœ์„œ๋กœ ๋ณ€ํ™˜ / Convert ports to network byte order

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // --- 3. ๋ฆฌ์Šค๋‹ ์‹œ์ž‘ (setsockopt: listen_socket(socket)) --- //
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "Server listening on port " << port_number << "..." << std::endl;

    // --- 4. ๋ฉ”์ธ ๋ฃจํ”„ (Accept loop): ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์„ ๋ฌดํ•œํžˆ ๊ธฐ๋‹ค๋ฆผ / Infinitely waiting for client connections --- //
    while (true) {

        // IPv4๊ตฌ์กฐ์ฒด / Structure for IPv4 address
        struct sockaddr_in client_address;

        // ์ฃผ์†Œ ๊ตฌ์กฐ์ฒด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ฐจ์ง€ํ•˜๋Š” ๋ฐ”์ดํŠธ ํฌ๊ธฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ณ€์ˆ˜์˜ ํƒ€์ž…
        // Type of the variable storing the byte size occupied by the address structure in memory
        socklen_t addrlen = sizeof(client_address);

        // accept() ํ•จ์ˆ˜๊ฐ€ ๋ธ”๋กœํ‚น(Blocking) ์ƒํƒœ์—์„œ ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์„ ๊ธฐ๋‹ค๋ฆผ
        // The accept() function waits for client connections in a blocking state.
        int client_socket = accept(server_fd, (struct sockaddr *)&client_address, &addrlen);
        if (client_socket < 0) {
            perror("accept failed");
            continue; // ์‹คํŒจํ–ˆ์œผ๋ฉด ๋‹ค์Œ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉฐ ์žฌ์‹œ๋„ / Retry in the next loop if it fails
        }

        // ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ: ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ์ด ์˜ค๋ฉด ์ƒˆ๋กœ์šด ์“ฐ๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ฒ˜๋ฆฌ
        // Concurrency Handling: When a new connection arrives, create a new thread to handle it.
        //std::thread client_thread(Server::handle_client, client_socket);
        //
        // ๋žŒ๋‹ค {[](){...}} ๋Š” ํ˜„์žฌ ์Šค๋ ˆ๋“œ๊ฐ€ Server ๊ฐ์ฒด(this)์˜ ๋งฅ๋ฝ์„ ์œ ์ง€ํ•˜๋ฉฐ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
        // Lambda {[](){...}} ensures that the current thread executes while maintaining the context of the Server object (this).
        std::thread client_thread([this, client_socket]() {
            this->handle_client(client_socket);
        });
        // ์“ฐ๋ ˆ๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ๊ณ„์† ์‹คํ–‰ํ•˜๊ฒŒ ํ•จ
        // Detach the thread so that the main loop continues running.
        client_thread.detach();
    }
}
//โ—๏ธ
void Server::handle_client(int client_socket) {
    char buffer[1024];
    std::cout << "[Connection] New client connected." << std::endl;

    // 1. ๋ฐ์ดํ„ฐ ์ˆ˜์‹  / receive data (Read)
    ssize_t bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0'; // C-string
        std::string request = buffer;
        std::cout << "[Received] " << request.substr(0, 30) << "..." << std::endl;

        // 2. ์‘๋‹ต ์ „์†ก / send response(Write)
        const char* response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<h1>๐Ÿ‘‹Hi~ Hi~ Hi~~~!</h1><p>Welcome to the miniWebserver!</p>";
        write(client_socket, response, strlen(response));
    }

    // 3. ์—ฐ๊ฒฐ ์ข…๋ฃŒ / close connection
    close(client_socket);
    std::cout << "[Connection] Client disconnected." << std::endl;
}

bool Server::start() {
    // ์„œ๋ฒ„๊ฐ€ ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜๋Š” ๋ฉ”์ธ ํ•จ์ˆ˜ / Main function that actually runs the server
    try {
        run_listener();
    } catch (const std::exception& e) {
        std::cerr << "[ERROR] Server failed to start: " << e.what() << std::endl;
        return false;
    }
    return true;
}

void Server::stop() {
    // ์„œ๋ฒ„๋ฅผ ์ข…๋ฃŒํ•˜๋Š” ํ•จ์ˆ˜ / Function to stop the server
    //close(server_fd);โ—๏ธ
    if (server_fd != -1) {
        // ๋ฉค๋ฒ„ ๋ณ€์ˆ˜์— ์ €์žฅ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. / Use the value stored in the member variable to stop the server
        close(server_fd);
        server_fd = -1; // ๋‹ซ์•˜์œผ๋ฏ€๋กœ ์ดˆ๊ธฐํ™” / Initialize to -1 after closing
    }
}

โœ”๏ธ Main.cpp

#include "Server.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <string>

int main() {

    // 1. ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ / Create server instance
    Server my_server("127.0.0.1", 5080); 
    std::thread server_thread(&Server::start, &my_server); // ์„œ๋ฒ„ ์‹œ์ž‘ / Start server โ—๏ธ
    std::cout << "--- Starting Server Application ---" << std::endl;

    // 2. ์ข…๋ฃŒ ๋กœ์ง (์„œ๋ฒ„๊ฐ€ ์ข…๋ฃŒ๋˜์ง€ ์•Š๊ธฐ ๋–„๋ฌธ์— ์‹คํ–‰๋˜์ง€ ์•Š์Œ,๋‚˜์ค‘์„ ์œ„ํ•œ ์ข…๋ฃŒ ๋กœ์ง)
    // Termination logic (not executed because the server has not terminated, termination logic for later)
    server_thread.join();
    my_server.stop();

    return 0;
}

โœ”๏ธ ์ปดํŒŒ์ผ / Compiling

g++ Server.cpp Main.cpp -o server -std=c++17 -pthread

โœ”๏ธ์‹คํ–‰ / Run

./server

Leave a Reply