[Webserver]miniWebserver – (1)

๐Ÿ‘‰๐Ÿป ์›น์„œ๋ฒ„์˜ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์›น์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
To understand the structure of a web server, I am going to try building a very simple web server.

๐Ÿ‘‰๐Ÿป ๊ฐ™์ด ์žฌ๋ฏธ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.
Let’s have fun making this together.

๐Ÿ‘‰๐Ÿป ์•„๋ž˜๋Š” ๋ธŒ๋ผ์šฐ์ €์— ๊ฐ„๋‹จํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
The following outputs a simple message to the browser.

๐Ÿ‘‰๐Ÿป ์„ค๋ช…์€ ์ฝ”๋“œ์ฃผ์„์„ ์ฐธ๊ณ ํ•˜์„ธ์š”
Please refer to the code comments for the explanation.

โœ”๏ธ ์ „์ฒด์ฝ”๋“œ / full code

#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

// ํด๋ผ์ด์–ธํŠธ์™€์˜ ํ†ต์‹ ์„ ๋‹ด๋‹นํ•˜๋Š” ํ•จ์ˆ˜ (๊ฐ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ๋ณ„๋„์˜ ์“ฐ๋ ˆ๋“œ)
// Function responsible for communication with clients (separate thread for each user)
void 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;
}

int main() {
    // --- 1. ์†Œ์ผ“ ์ƒ์„ฑ (Socket Creation) --- //
    // AF_INET(Address Family Internet):
    // IPv4 ์‚ฌ์šฉ, SOCK_STREAM: TCP ์—ฐ๊ฒฐ ๋ฐฉ์‹ ์‚ฌ์šฉ, 0: ํ”„๋กœํ† ์ฝœ ๊ธฐ๋ณธ ์‚ฌ์šฉ(๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ)
    // Use IPv4, SOCK_STREAM: Use TCP connection method, 0: Use default protocol (use default)
    //
    // AF_INET6: IPv6 ์‚ฌ์šฉ, SOCK_DGRAM : UDP ์—ฐ๊ฒฐ ๋ฐฉ์‹ ์‚ฌ์šฉ / Use UDP connection method
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        // perror() : ์–ด๋–ค ์‹œ์Šคํ…œ ํ•จ์ˆ˜๊ฐ€ ์‹คํŒจํ•  ๊ฒฝ์šฐ ๋‚ด๊ฐ€ ์ •์˜ํ•œ ์˜ค๋ฅ˜ ๋ฉ”์„ธ์ง€์™€ ์šด์˜์ฒด์ œ๊ฐ€ ์ •์˜ํ•œ ์˜ค๋ฅ˜ ๋ฉ”์„ธ์ง€๋ฅผ ํ•จ๊ป˜ ์ถœ๋ ฅํ•ด์คŒ.
        perror("socket failed");
        /*
         exit(1)์˜ ๋™์ž‘ ๊ฒฐ๊ณผ๋Š” exit(EXIT_FAILURE)๊ฐ€ ์˜๋„ํ•˜๋Š” ๋™์ž‘ ๊ฒฐ๊ณผ์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํ”„๋กœ๊ทธ๋žจ์ด '์‹คํŒจ' ์ƒํƒœ๋กœ ์ข…๋ฃŒ๋œ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
         exit(0)์˜ ๋™์ž‘ ๊ฒฐ๊ณผ๋Š” exit(EXIT_SUCCESS)๊ฐ€ ์˜๋„ํ•˜๋Š” ๋™์ž‘ ๊ฒฐ๊ณผ์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํ”„๋กœ๊ทธ๋žจ์ด '์„ฑ๊ณต' ์ƒํƒœ๋กœ ์ข…๋ฃŒ๋œ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
         The result of exit(1) is the same as the intended result of exit(EXIT_FAILURE). In other words, it means the same thing: the program terminates in a 'failed' state.
         The result of exit(0) is the same as the intended result of exit(EXIT_SUCCESS). In other words, it means the same thing: the program terminates in a 'successful' state.
         */
        exit(EXIT_FAILURE);
    }

    // ํฌํŠธ ์žฌ์‚ฌ์šฉ ์˜ต์…˜ ์„ค์ • (๊ฐ™์€ ํฌํŠธ๋ฅผ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ)
    /* setsockopt() : ์†Œ์ผ“์—์„ค์ •์ ์šฉํ•˜๊ธฐ / Applying settings to the socket
     * setsockopt(int sockfd,           // 1. ์†Œ์ผ“ ์‹๋ณ„์ž / socket identifier
                int level,              //.2. ํ”„๋กœํ† ์ฝœ ๋ ˆ๋ฒจ (์–ด๋А ๊ณ„์ธต์˜ ์„ค์ •์ธ์ง€) / Protocol level (which layer's setting)
                int optionname,         // 3. ์„ค์ •ํ•  ์˜ต์…˜์˜ ์ด๋ฆ„ (์–ด๋–ค ์„ค์ •์„ ํ• ์ง€) / Name of the option to set (which setting to make)
                const void *optionval,  // 4. ์˜ต์…˜์˜ ์‹ค์ œ ๊ฐ’ (๊ฐ’์„ 1๋กœ ์„ค์ •ํ• ์ง€) / The actual value of the option (whether to set the value to 1)
                socklen_t optionlen);   // 5. ์˜ต์…˜ ๊ฐ’์˜ ๊ธธ์ด / Length of the option value

       SOL_SOCKET :
        ์†Œ์ผ“ ํ†ต๋กœ ์ž์ฒด์˜ ์ „๋ฐ˜์ ์ธ ์˜ต์…˜ ๋ฐ ์†์„ฑ(์†Œ์ผ“ํ†ต์‹ ์„ ์œ„ํ•œ ๊ธฐ๋ณธ ๊ฐ’)
        Overall options and properties of the socket channel itself (default values โ€‹โ€‹for socket communication)

       SO_REUSEADDR :
        ์ง€๊ธˆ ์ด ํฌํŠธ๋Š” ์ž„์‹œ์ ์œผ๋กœ ์ ์œ ๋œ ์ƒํƒœ์ผ์ง€๋ผ๋„, ์ด ์ฃผ์†Œ๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๋„๋ก ์š”์ฒญํ•˜๋Š” ๊ฒƒ
        Requesting to use this address again, even if this port is currently temporarily occupied

       opt = 1 : ํฌํŠธ ์žฌ์‚ฌ์šฉ ํ—ˆ์šฉ / Allow port reuse

    */
    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 :
    //   IPv4 ์ฃผ์†Œ๋งŒ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
    //   This is a specific structure for handling only IPv4 addresses.
    //
    // struct sockaddr_in6 :
    //   IPv6 ์ฃผ์†Œ๋งŒ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
    //   This is a specific structure designed to handle only IPv6 addresses.
    //
    // struct sockaddr :
    //   ์ด ๊ตฌ์กฐ์ฒด๋Š” ํŠน์ • IP ๋ฒ„์ „(IPv4์ธ์ง€ IPv6์ธ์ง€)์— ๊ด€๊ณ„์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    //   This structure is designed to be usable regardless of the specific IP version (whether IPv4 or IPv6).

    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);       // ํฌํŠธ๋ฅผ ๋„คํŠธ์›Œํฌ ๋ฐ”์ดํŠธ ์ˆœ์„œ๋กœ ๋ณ€ํ™˜ / Convert ports to network byte order

    // ๋ฐ”๋กœ ์œ„์—์„œ ์„ ์–ธํ•œ ๊ตฌ์กฐ์ฒด address์— ์†Œ์ผ“์„ ๋ฐ”์ธ๋”ฉํ•จ.
    // Bind the socket to the structure address declared just above.
    //
    // (struct sockaddr *)&address : &address๋ณ€์ˆ˜์˜ ๊ฐ’์„ (struct sockaddr *) ํƒ€์ž…์œผ๋กœ ํƒ€์ž… ์บ์ŠคํŒ…ํ•จ.
    // (struct sockaddr *)&address : Type casts the value of the &address variable to the type (struct sockaddr *).

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

    // --- 3. ๋ฆฌ์Šค๋‹ (Listening) --- //
    // ์ตœ๋Œ€ ๋Œ€๊ธฐํ•  ์—ฐ๊ฒฐ ์š”์ฒญ ์ˆ˜๋ฅผ ์ง€์ • (BACKLOG)
    // Specify the maximum number of connection requests to wait for (BACKLOG)
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    std::cout << "Server listening on port " << PORT << "..." << std::endl;

    // --- 4. ๋ฌดํ•œ ๋ฃจํ”„: ์—ฐ๊ฒฐ ์ˆ˜๋ฝ ๋ฐ ์ฒ˜๋ฆฌ / Accept and handle incoming connections indefinitely --- //
    while (true) {
        // IPv4๊ตฌ์กฐ์ฒด
        struct sockaddr_in client_address;
        // ์ฃผ์†Œ ๊ตฌ์กฐ์ฒด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ฐจ์ง€ํ•˜๋Š” ๋ฐ”์ดํŠธ ํฌ๊ธฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ณ€์ˆ˜์˜ ํƒ€์ž…
        socklen_t addrlen = sizeof(client_address);

        // accept() ํ•จ์ˆ˜๊ฐ€ ๋ธ”๋กœํ‚น(Blocking) ์ƒํƒœ์—์„œ ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์„ ๊ธฐ๋‹ค๋ฆผ
        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
        }

        // 5. ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ: ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ์ด ์˜ค๋ฉด ์ƒˆ๋กœ์šด ์“ฐ๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ฒ˜๋ฆฌ
        // Concurrency Handling: When a new connection arrives, create a new thread to handle it.
        std::thread client_thread(handle_client, client_socket);
        // ์“ฐ๋ ˆ๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ๊ณ„์† ์‹คํ–‰ํ•˜๊ฒŒ ํ•จ
        // Detach the thread so that the main loop continues running.
        client_thread.detach();
    }

    // ์ด ๋ถ€๋ถ„์€ ๋ฌดํ•œ ๋ฃจํ”„๊ฐ€ ๋„๋‹ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. / This part is not reached by an infinite loop.
    close(server_fd);
    return 0;
}

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

g++ miniserver.cpp -o miniserver

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

./miniserver

โœ”๏ธ๋ธŒ๋ผ์šฐ์ €์ ‘์† / Access Browser

— ๋ธŒ๋ผ์šฐ์ €์— ์•„๋ž˜์˜ ์ฃผ์†Œ๋กœ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค. / Access the following address in your browser.

http://localhost:5080

— ํ„ฐ๋ฏธ๋„๊ณผ ๋ธŒ๋ผ์šฐ์ €์— ๋ฉ”์„ธ์ง€๊ฐ€ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. / Messages are displayed in the terminal and browser.

Leave a Reply