👉🏻 주요 변경사항은 다음과 같습니다.
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