반응형
멀티스레딩을 이용한 동시성 처리
멀티스레딩을 사용하면 여러 클라이언트의 요청을 동시에 처리할 수 있어 서버의 성능을 크게 향상시킬 수 있습니다. 오늘은 C++ 표준 라이브러리의 스레드를 사용하여 웹 서버의 동시성 처리를 구현하겠습니다.
멀티스레딩의 필요성
싱글 스레드 서버는 하나의 요청을 처리하는 동안 다른 요청을 기다리게 합니다. 멀티스레딩을 사용하면 여러 요청을 동시에 처리할 수 있어 응답 시간을 줄이고 서버의 처리 능력을 향상시킬 수 있습니다.
멀티스레딩 구현
1. 스레드 풀 구현
스레드 풀은 일정 수의 스레드를 미리 생성하고, 작업을 큐에 추가하여 스레드가 작업을 처리하도록 합니다. 이를 통해 스레드 생성 및 소멸의 오버헤드를 줄일 수 있습니다.
thread_pool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
class ThreadPool {
public:
ThreadPool(size_t numThreads);
~ThreadPool();
void enqueue(std::function<void()> task);
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queueMutex_;
std::condition_variable condition_;
std::atomic<bool> stop_;
void worker();
};
#endif // THREAD_POOL_H
thread_pool.cpp
#include "thread_pool.h"
ThreadPool::ThreadPool(size_t numThreads) : stop_(false) {
for (size_t i = 0; i < numThreads; ++i) {
workers_.emplace_back([this] { this->worker(); });
}
}
ThreadPool::~ThreadPool() {
stop_ = true;
condition_.notify_all();
for (std::thread& worker : workers_) {
if (worker.joinable()) {
worker.join();
}
}
}
void ThreadPool::enqueue(std::function<void()> task) {
{
std::unique_lock<std::mutex> lock(queueMutex_);
tasks_.push(task);
}
condition_.notify_one();
}
void ThreadPool::worker() {
while (!stop_) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex_);
condition_.wait(lock, [this] { return !tasks_.empty() || stop_; });
if (stop_ && tasks_.empty()) {
return;
}
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
}
2. 서버에 스레드 풀 통합
서버에서 스레드 풀을 사용하여 클라이언트 요청을 처리하도록 합니다.
server.h
#ifndef SERVER_H
#define SERVER_H
#include <string>
#include <netinet/in.h>
#include "router.h"
#include "thread_pool.h"
class Server {
public:
Server(const std::string& address, int port, size_t numThreads);
~Server();
void start();
void stop();
void addRoute(const std::string& path, Router::Handler handler);
private:
void handleClient(int clientSocket);
std::string address_;
int port_;
int serverSocket_;
std::atomic<bool> running_;
struct sockaddr_in serverAddr_;
Router router_;
ThreadPool threadPool_;
};
#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, size_t numThreads)
: address_(address), port_(port), running_(false), threadPool_(numThreads) {
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;
}
threadPool_.enqueue([this, clientSocket] { handleClient(clientSocket); });
}
}
void Server::stop() {
running_ = false;
}
void Server::addRoute(const std::string& path, Router::Handler handler) {
router_.addRoute(path, handler);
}
void Server::handleClient(int clientSocket) {
char buffer[1024];
int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived < 0) {
std::cerr << "Failed to receive request" << std::endl;
close(clientSocket);
return;
}
buffer[bytesReceived] = '\0';
std::string requestStr(buffer);
Request request(requestStr);
std::string path = request.getPath();
router_.handleRequest(path, clientSocket);
close(clientSocket);
}
3. 서버 실행
서버를 실행하여 여러 클라이언트의 요청을 동시에 처리할 수 있습니다.
main.cpp
#include "server.h"
#include "response.h"
void handleRoot(int clientSocket) {
Response response(200);
response.setHeader("Content-Type", "text/html");
response.setBody("<html><body><h1>Welcome to the Home Page!</h1></body></html>");
std::string responseStr = response.toString();
send(clientSocket, responseStr.c_str(), responseStr.size(), 0);
}
void handleAbout(int clientSocket) {
Response response(200);
response.setHeader("Content-Type", "text/html");
response.setBody("<html><body><h1>About Us</h1></body></html>");
std::string responseStr = response.toString();
send(clientSocket, responseStr.c_str(), responseStr.size(), 0);
}
int main() {
try {
Server server("127.0.0.1", 8080, 4); // 4개의 스레드를 가진 스레드 풀 사용
server.addRoute("/", handleRoot);
server.addRoute("/about", handleAbout);
server.start();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
이제 서버를 실행하고 여러 클라이언트의 요청을 동시에 처리할 수 있는지 테스트해보겠습니다. 멀티스레딩을 통해 서버의 성능이 향상되는 것을 확인할 수 있습니다.
이제 18일차의 학습을 마쳤습니다. 멀티스레딩을 사용하여 웹 서버의 동시성 처리를 구현해보았습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "에러 핸들링 및 로깅"에 대해 학습하고 구현하겠습니다.
반응형
'-----ETC----- > C++ 고급 프로그래밍과 응용 프로젝트 시리즈' 카테고리의 다른 글
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 20: 프로젝트 1 - 최적화 및 성능 테스트 (0) | 2024.08.01 |
---|---|
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 21: 프로젝트 1 - 배포 및 유지보수 (0) | 2024.08.01 |
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 19: 프로젝트 1 - 에러 핸들링 및 로깅 (0) | 2024.08.01 |
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 16: 프로젝트 1 - HTTP 프로토콜 이해 및 구현 (0) | 2024.08.01 |
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 17: 프로젝트 1 - 요청 처리 및 라우팅 구현 (0) | 2024.08.01 |