๐๐ป ์น์๋ฒ์ ๊ตฌ์กฐ๋ฅผ ์ดํดํ๊ธฐ ์ํด์ ์์ฃผ ๊ฐ๋จํ ์น์๋ฒ๋ฅผ ๋ง๋ค์ด๋ณด๋ ค๊ณ ํฉ๋๋ค.
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.

