본문 바로가기
-----ETC-----/C++ 고급 프로그래밍과 응용 프로젝트 시리즈

[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 15: 실전 프로젝트 - 프로젝트 소개 및 설계

by cogito21_cpp 2024. 8. 1.
반응형

프로젝트 1: 간단한 웹 서버

이번 프로젝트에서는 간단한 웹 서버를 구축해보겠습니다. 이 웹 서버는 HTTP 요청을 처리하고, 정적 파일을 제공하는 기능을 구현할 것입니다. 이를 통해 네트워크 프로그래밍, 스레드, 파일 입출력 등의 개념을 실습할 수 있습니다.

 

프로젝트 목표

  1. HTTP 프로토콜 이해: HTTP 요청과 응답 구조를 이해하고 구현합니다.
  2. 소켓 프로그래밍: TCP 소켓을 사용하여 클라이언트와 서버 간의 통신을 구현합니다.
  3. 멀티스레딩: 여러 클라이언트 요청을 동시에 처리하기 위해 멀티스레딩을 구현합니다.
  4. 정적 파일 제공: 서버가 정적 파일 (HTML, CSS, JS)을 제공할 수 있도록 합니다.

프로젝트 설계

1. HTTP 요청과 응답

HTTP 프로토콜은 클라이언트와 서버 간의 요청/응답 프로토콜입니다. HTTP 요청은 메서드, 경로, 버전, 헤더, 바디로 구성됩니다. HTTP 응답은 상태 코드, 헤더, 바디로 구성됩니다.

 

HTTP 요청 예시:

GET /index.html HTTP/1.1
Host: localhost:8080

 

HTTP 응답 예시:

HTTP/1.1 200 OK
Content-Type: text/html

<html>
<head>
    <title>Test</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

 

2. 소켓 프로그래밍

TCP 소켓을 사용하여 클라이언트와 서버 간의 통신을 구현합니다. 서버는 클라이언트의 요청을 받아들이고, 적절한 응답을 보냅니다.

 

소켓 프로그래밍 기본 구조:

  1. 서버 소켓 생성
  2. 소켓 바인딩
  3. 소켓 리스닝
  4. 클라이언트 연결 수락
  5. 데이터 송수신

 

3. 멀티스레딩

멀티스레딩을 사용하여 여러 클라이언트 요청을 동시에 처리합니다. 각 클라이언트 연결은 별도의 스레드에서 처리됩니다.

 

4. 정적 파일 제공

서버는 클라이언트의 요청에 따라 정적 파일 (HTML, CSS, JS)을 제공합니다. 파일 시스템에서 요청된 파일을 읽어 클라이언트에 전송합니다.


프로젝트 구현

프로젝트 구조

/webserver
    /src
        main.cpp
        server.h
        server.cpp
        request.h
        request.cpp
        response.h
        response.cpp
    /www
        index.html
        styles.css

 

1. 서버 설정

먼저, 서버 설정을 위한 클래스를 정의합니다.

 

server.h

#ifndef SERVER_H
#define SERVER_H

#include <string>
#include <thread>
#include <vector>
#include <atomic>
#include <netinet/in.h>

class Server {
public:
    Server(const std::string& address, int port);
    ~Server();

    void start();
    void stop();

private:
    void handleClient(int clientSocket);

    std::string address_;
    int port_;
    int serverSocket_;
    std::vector<std::thread> threads_;
    std::atomic<bool> running_;
    struct sockaddr_in serverAddr_;
};

#endif // SERVER_H

 

server.cpp

#include "server.h"
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>

Server::Server(const std::string& address, int port) : address_(address), port_(port), running_(false) {
    serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket_ == -1) {
        throw std::runtime_error("Failed to create socket");
    }

    serverAddr_.sin_family = AF_INET;
    serverAddr_.sin_addr.s_addr = inet_addr(address.c_str());
    serverAddr_.sin_port = htons(port);

    if (bind(serverSocket_, (struct sockaddr*)&serverAddr_, sizeof(serverAddr_)) == -1) {
        throw std::runtime_error("Failed to bind socket");
    }

    if (listen(serverSocket_, 10) == -1) {
        throw std::runtime_error("Failed to listen on socket");
    }
}

Server::~Server() {
    stop();
    close(serverSocket_);
}

void Server::start() {
    running_ = true;
    while (running_) {
        int clientSocket = accept(serverSocket_, nullptr, nullptr);
        if (clientSocket == -1) {
            std::cerr << "Failed to accept client" << std::endl;
            continue;
        }
        threads_.emplace_back(&Server::handleClient, this, clientSocket);
    }
}

void Server::stop() {
    running_ = false;
    for (auto& thread : threads_) {
        if (thread.joinable()) {
            thread.join();
        }
    }
}

void Server::handleClient(int clientSocket) {
    // 클라이언트 요청 처리
    close(clientSocket);
}

 

2. HTTP 요청과 응답 처리

HTTP 요청과 응답을 처리하는 클래스를 정의합니다.

 

request.h

#ifndef REQUEST_H
#define REQUEST_H

#include <string>
#include <unordered_map>

class Request {
public:
    Request(const std::string& rawRequest);

    std::string getMethod() const;
    std::string getPath() const;
    std::string getVersion() const;
    std::string getHeader(const std::string& name) const;

private:
    void parseRequest(const std::string& rawRequest);

    std::string method_;
    std::string path_;
    std::string version_;
    std::unordered_map<std::string, std::string> headers_;
};

#endif // REQUEST_H

 

request.cpp

#include "request.h"
#include <sstream>

Request::Request(const std::string& rawRequest) {
    parseRequest(rawRequest);
}

void Request::parseRequest(const std::string& rawRequest) {
    std::istringstream stream(rawRequest);
    std::string line;
    std::getline(stream, line);
    std::istringstream requestLine(line);
    requestLine >> method_ >> path_ >> version_;

    while (std::getline(stream, line) && line != "\r") {
        size_t colon = line.find(':');
        if (colon != std::string::npos) {
            std::string name = line.substr(0, colon);
            std::string value = line.substr(colon + 2, line.size() - colon - 3);
            headers_[name] = value;
        }
    }
}

std::string Request::getMethod() const {
    return method_;
}

std::string Request::getPath() const {
    return path_;
}

std::string Request::getVersion() const {
    return version_;
}

std::string Request::getHeader(const std::string& name) const {
    auto it = headers_.find(name);
    if (it != headers_.end()) {
        return it->second;
    }
    return "";
}

 

response.h

#ifndef RESPONSE_H
#define RESPONSE_H

#include <string>
#include <unordered_map>

class Response {
public:
    Response(int statusCode);

    void setHeader(const std::string& name, const std::string& value);
    void setBody(const std::string& body);
    std::string toString() const;

private:
    int statusCode_;
    std::unordered_map<std::string, std::string> headers_;
    std::string body_;
};

#endif // RESPONSE_H

 

response.cpp

#include "response.h"
#include <sstream>

Response::Response(int statusCode) : statusCode_(statusCode) {}

void Response::setHeader(const std::string& name, const std::string& value) {
    headers_[name] = value;
}

void Response::setBody(const std::string& body) {
    body_ = body;
}

std::string Response::toString() const {
    std::ostringstream response;
    response << "HTTP/1.1 " << statusCode_ << "\r\n";
    for (const auto& header : headers_) {
        response << header.first << ": " << header.second << "\r\n";
    }
    response << "\r\n" << body_;
    return response.str();
}

 

3. 서버 실행

이제 서버를 실행하는 코드를 작성합니다.

 

main.cpp

#include "server.h"

int main() {
    try {
        Server server("127.0.0.1", 8080);
        server.start();
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}

 

이제 프로젝트의 기본 설계와 일부 구현을 마쳤습니다. 앞으로 며칠 동안 이 프로젝트를 확장하여 HTTP 프로토콜 구현, 멀티스레딩, 정적 파일 제공 기능 등을 추가하겠습니다.

 

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "HTTP 프로토콜 이해 및 구현"에 대해 학습하고 구현하겠습니다.

반응형