๐๐ป ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
The major changes are as follows.
โ๏ธ ๊ธฐ์กด์ ์ปค๋งจ๋ ์ข
๋ฃ ๋ถ๋ถ์ ํจ์๋ก ๋ณ๊ฒฝํ์ต๋๋ค.(main.cpp)
I changed the existing command termination part to a function. (main.cpp)
my_server.run_cmd();
โ๏ธ ๋ผ์ฐํฐ ์ค์ ํ์ผ์ ์ถ๊ฐํ์ต๋๋ค.
I added the router configuration file.
Router.h Router.cpp
โ๏ธ ๋ผ์ฐํฐ์ ๋ง์ํ์
์ ์ถ๊ฐํ์ต๋๋ค.(์์ง์ body๋ง ๋ฆฌํด)
Added MIME types to the router. (Currently only returns body)
๐๐ป ๊ธฐ์กด ์ฝ๋์์ ์ถ๊ฐํ๊ฑฐ๋ ๋ณ๊ฒฝ๋ ๋ถ๋ถ์ โ๏ธ๋ก ํ์๋์ด ์์ต๋๋ค.
Parts added or changed from the existing code are marked with โ๏ธ.
๐๐ป ๋๋จธ์ง ์ค๋ช
์ ์ฝ๋ ์ฃผ์์ ์ฐธ๊ณ ํ์ธ์
Please refer to the code comments for the rest of the explanation.
๐๐ป ์ฝ๋ / Code
โ๏ธ Main.cpp
#include "Server.h" // ์์ผ์๋ฒ/Scoket Server
#include "Router.h" // ๋ผ์ฐํฐ/routerโ๏ธ
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <string>
#include <map>
#include <utility> // std::pair ์ฌ์ฉ์ ์ํด ์ถ๊ฐ / Added for using std::pair โ๏ธ
int main() {
Server my_server("127.0.0.1", 5080);
Router my_router; // ๋ผ์ฐํฐ ์ธ์คํด์ค / router instance โ๏ธ
// ํธ๋ค๋ฌ ์ค์ - ๋ง์ํ์
์ ์ฉ โ๏ธ
// Handler Settings - Apply Mime Type
my_server.set_handler([&](const std::string& req) {
auto [mime, body] = my_router.router(req);
// ์ฌ๊ธฐ์ mime๋ ๊ฐ์ด ๋๊ฒจ์ค์ผ ํ์ง๋ง, ํ์ฌ๋ body๋ง ๋ฆฌํดํ๋ ๊ตฌ์กฐ์
๋๋ค.
// Although the mime should also be passed here, the current structure only returns the body.
return body;
});
// 3. ์๋ฒ ์์ง ์์ (์ค๋ ๋) / Start server engine (thread)
std::thread server_thread(&Server::start, &my_server);
std::cout << "--- Starting Server Application ---" << std::endl;
// 4. ์ปค๋งจ๋ ์
๋ ฅ ๋ฃจํ (์ค๋ ๋๋ก ๋๋ฆฌ์ง ๋ง๊ณ ๋ฉ์ธ์์ ์ง์ ์คํ) โ๏ธ
// ๋ฉ์ธ ์ค๋ ๋๊ฐ ์ฌ๊ธฐ์ ์
๋ ฅ์ ๋๊ธฐํ๋ฏ๋ก ํ๋ก๊ทธ๋จ์ด ๋ฐ๋ก ์ข
๋ฃ๋์ง ์์ต๋๋ค.
// 4. Command Input Loop (Do not run as a thread, execute directly in Main)
// The main thread waits for input here, so the program does not terminate immediately.
my_server.run_cmd();
// 5. run_cmd()๋ฅผ ๋น ์ ธ๋์๋ค๋ ๊ฒ์ ์ฌ์ฉ์๊ฐ exit๋ฅผ ์ณค๋ค๋ ๋ป์ด๋ฏ๋ก ์ ๋ฆฌ ์์
// 5. Exiting run_cmd() means the user typed exit, so start cleaning up.
my_server.stop();
if (server_thread.joinable()) {
server_thread.join();
}
std::cout << "[MAIN] Server Application Finished." << std::endl;
return 0;
}
โ๏ธ Server.h
#include <string>
#include <atomic>
#include <functional>
class Server {
public:
// ์์ฑ์: ์๋ฒ ์ด๊ธฐํ (IP, Port)
// Constructor: Initialize server (IP, Port)
Server(const std::string& ip, int port);
// ์๋ฉธ์ / Destructor
~Server() {
stop();
}
// ์๋ฒ ์์ผ ํ์ผ ๋์คํฌ๋ฆฝํฐ๋ฅผ ์ ์ฅํ ๋ฉค๋ฒ ๋ณ์
// Member variable to store server socket file descriptors
int server_fd = -1;
// ์๋ฒ ์คํ ํจ์: ๋ฆฌ์ค๋์ ์์ํ๊ณ ๋ฉ์ธ ๋ฃจํ๋ฅผ ๋๋ฆผ
// Server execution function: Starts listening and runs the main loop
bool start();
// ์๋ฒ ์ข
๋ฃ / Server close(ํ์ํ๋ค๋ฉด / if neccessary)
// Shut down the server (if necessary)
void stop();
// ์ปค๋งจ๋ ๋ก์ง ๊ตฌํ / implement command loginโ๏ธ
void run_cmd();
// -- ๋ธ๋ผ์ฐ์ ๋ฉ์ธ์ง ๋ฐ๊พธ๊ธฐ / Change browser message --
// string(์์ฒญ)์ ๋ฐ์์ string(์๋ต๋ด์ฉ)์ ๋ฑ๋ ํจ์๋ฅผ ๋ด๋ ๊ทธ๋ฆ
// A container for a function that takes a string (request) and returns a string (response content)
std::function<std::string(const std::string&)> logic_handler;
// main์์ ๋๋ค๋ฅผ ๋ฃ์ด์ค ํจ์(Main.cpp์์์ฌ์ฉ)
// Function to pass the lambda in main (used in Main.cpp)
void set_handler(std::function<std::string(const std::string&)> handler) {
logic_handler = handler;
}
void handle_client_test(int client_socket);
private:
std::string ip_address;
int port_number;
// ์๋ฒ๊ฐ ์คํ ์ํ์ธ์ง ํ์ธํ๋ ํ๋๊ทธ
// Flag to check if the server is running
std::atomic<bool> running{false};
// ์ค์ ๋ฆฌ์ค๋ ๋ฐ ์ฐ๊ฒฐ ์์ ๋ก์ง (๋ด๋ถ ๊ตฌํ)
// 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>
#include <functional> // ์ถ๊ฐ / add
#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 --- //
server_fd = socket(AF_INET, SOCK_STREAM, 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 --- //
// ๋ฌดํ๋ฃจํ์์ ์ข
๋ฃ์ ํธ๋ฅผ ๋ฐ์ผ๋ฉด ๋ฃจํ๋ฅผ ์ข
๋ฃ / Exit the loop when the termination signal is received
while (running.load()) {
// ์ข
๋ฃ์ ํธ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํด 0.1์ด ๋๊ธฐ / Wait for 0.1 seconds to check the termination signal
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // ๋ถํ ๋ฐฉ์ง๋ฅผ ์ํด ๋๊ธฐ
// IPv4๊ตฌ์กฐ์ฒด / Structure for IPv4 address
struct sockaddr_in client_address;
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 (running.load() && client_socket < 0) { //
perror("accept failed");
continue; // ์คํจํ์ผ๋ฉด ๋ค์ ๋ฃจํ๋ฅผ ๋๋ฉฐ ์ฌ์๋ / Retry in the next loop if it fails
}
// ํด๋ผ์ด์ธํธ ์ ์ / Client connected
std::thread client_thread([this, client_socket]() {
this->handle_client(client_socket); //์ธ๋ถ์์ ๋ฉ์ธ์ง ๋ฐ๊พธ๊ธฐ
});
client_thread.detach();
}
}
// ์ปค๋งจ๋ ์
๋ ฅ ๋ฃจํ / Command input loop โ๏ธ
void Server::run_cmd(){
std::string command;
while (true) {
std::cout << "CMD> "; // ๋ช
๋ น์ด ํ๋กฌํํธ ํ์ / Show command prompt
// std::getline์ ์ฌ์ฉํ์ฌ ๊ณต๋ฐฑ์ ํฌํจํ ์ ์ฒด ๋ฌธ์์ด์ ํ ๋ฒ์ ๋ฐ์ต๋๋ค.
// Use std::getline to get the entire string including spaces at once.
if (!std::getline(std::cin, command)) {
// EOF (Ctrl+C ๋ฑ)๋ก ์
๋ ฅ์ ๋ฐ์ ์ ์๋ ์ํฉ์ด ๋ฐ์ํ๋ฉด ์ข
๋ฃ
// Terminate if input cannot be received due to EOF (Ctrl+C, etc.)
break;
}
// ์
๋ ฅ๋ ๋ช
๋ น์ด๋ฅผ ํ์ธํฉ๋๋ค. / Check the input command
if (command.empty()) {
// ์๋ฌด๊ฒ๋ ์
๋ ฅํ์ง ์์์ผ๋ฉด ๊ทธ๋ฅ ๋์ด๊ฐ๋๋ค./ If you don't enter anything, it will just skip.
continue;
} else if (command == "/exit" || command == "exit") {
std::cout << "[INFO] ์ข
๋ฃ ๋ช
๋ น์ ๋ฐ์์ต๋๋ค. ์๋น์ค ์ข
๋ฃ๋ฅผ ์์ํฉ๋๋ค.\n";
std::cout << "[INFO] Received a shutdown command. Starting service shutdown.\n";
break; // ๋ฃจํ ์ข
๋ฃ / Loop End
} else if (command == "/status") {
std::cout << "[STATUS] ์๋น์ค๋ ์ ์์ ์ผ๋ก ์๋ ์ค์
๋๋ค.\n";
std::cout << "[STATUS] The service is running normally.\n";
} else {
std::cout << "[ERROR] ์ ์ ์๋ ๋ช
๋ น์ด์
๋๋ค. (/exit, /status)\n";
std::cout << "[ERROR] Unknown command. (/exit, /status)\n";
}
}
}
// ๋๋ค์์ผ๋ก ์ธ๋ถ์์ ๋ด์ฉ ๋ฐ๊พธ๊ธฐ
// Change content from the outside using a lambda expression
void Server::handle_client(int client_socket) {
char buffer[1024];
std::cout << "[Connection] New client connected." << std::endl;
// 1. ๋ฐ์ดํฐ ์์ / receive data
ssize_t bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
std::string request = buffer;
std::cout << "[Received] " << request.substr(0, 30) << "..." << std::endl;
std::string html_content;
if (logic_handler) {
// main์์ ๋ณด๋ธ ๋๋ค์ ์คํ,request๋ ๋ฒํผ์ ๊ฐ...
// Execute the lambda expression sent from main, request is the buffer value...
html_content = logic_handler(request);
} else {
html_content = "<h1>No Logic Handler set!</h1>";
}
// HTTP ํฌ๋งท ์๋ต / send HTTP response
std::string response = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: " + std::to_string(html_content.size()) + "\r\n"
"\r\n" + html_content;
write(client_socket, response.c_str(), response.size());
}
// 2. ์ฐ๊ฒฐ ์ข
๋ฃ (if๋ฌธ ๋ฐ์ผ๋ก ์ด๋ํ์ฌ ๋ฌด์กฐ๊ฑด ๋ซํ๊ฒ ์ค์ )
// 2. Close connection (move outside the if statement and set to close unconditionally)
close(client_socket);
std::cout << "[Connection] Client disconnected." << std::endl;
}
bool Server::start() {
// ์๋ฒ๊ฐ ์ค์ ๋ก ์๋ํ๋ ๋ฉ์ธ ํจ์ / Main function that actually runs the server
try {
if (!running.load()) {
running.store(true);
run_listener();
return false;
}
} catch (const std::exception& e) {
std::cerr << "[ERROR] Server failed to start: " << e.what() << std::endl;
return false;
}
return true;
}
void Server::stop() {
if (running.load() && server_fd != -1) { //
// ํ๋๊ทธ๋ฅผ false๋ก ์ค์ ํ์ฌ ๋ฃจํ ์ข
๋ฃ ์ ํธ ์ ์ก
// Set flag to false to send loop termination signal
running.store(false);
close(server_fd);
// ์ค์ ๋ก๋ ์ฌ๊ธฐ์ ์์ผ์ ๋ซ๊ฑฐ๋ ๋ฆฌ์์ค ํด์ ๋ฅผ ์ํํฉ๋๋ค.
// In practice, close the socket or release resources here.
server_fd = -1; // ๋ซ์์ผ๋ฏ๋ก ์ด๊ธฐํ / Initialize to -1 after closing
std::cout << "[Server] Shutdown initiated." << std::endl;
}
}
โ๏ธ Router.h
#ifndef ROUTER_H
#define ROUTER_H
#include <string>
#include <map>
#include <functional>
#include <utility> // std::pair ์ฌ์ฉ์ ์ํด ์ถ๊ฐ / add std::pair for use
class Router {
public:
Router(); // ์์ฑ์ ์ ์ธ / constructor declaration
std::pair<std::string, std::string> router(const std::string& request); // ํจ์ ์ ์ธ / function declarationโ๏ธ
};
#endif // ROUTER_H