본문으로 건너뛰기
Previous
Next
C++ Chat Server Architecture: Boost.Asio, Room Management,

C++ Chat Server Architecture: Boost.Asio, Room Management,

C++ Chat Server Architecture: Boost.Asio, Room Management,

이 글의 핵심

Build production C++ chat servers with Boost.Asio: acceptor-worker pattern, room management, message routing, connection pooling, and scalability.

Why Chat Server Architecture Matters

Problem: Real-Time Messaging at Scale

Problem: Chat servers must handle:

  • Thousands of concurrent connections
  • Low-latency message delivery
  • Room/channel management
  • Connection state tracking Solution: Async I/O (Boost.Asio) + Acceptor-Worker pattern + Room management + Message routing.
flowchart TD
    subgraph Clients
        C1[Client 1]
        C2[Client 2]
        C3[Client 3]
    end
    subgraph Server
        A[Acceptor]
        W1[Worker 1]
        W2[Worker 2]
        R[Room Manager]
        M[Message Router]
    end
    C1 -->|Connect| A
    C2 -->|Connect| A
    C3 -->|Connect| A
    A -->|Assign| W1
    A -->|Assign| W2
    W1 -->|Join Room| R
    W2 -->|Send Msg| M
    M -->|Broadcast| W1
    M -->|Broadcast| W2

Table of Contents

  1. Architecture Overview
  2. Boost.Asio Async I/O
  3. Acceptor-Worker Pattern
  4. Room Management
  5. Message Routing
  6. Connection Pooling
  7. Heartbeat & Timeout
  8. Graceful Shutdown
  9. Production Patterns
  10. Complete Example

1. Architecture Overview

Components

flowchart TD
    subgraph Client Layer
        C[Clients]
    end
    subgraph Network Layer
        A["Acceptor\nAccept connections"]
        W["Worker Pool\nHandle I/O"]
    end
    subgraph Business Layer
        R["Room Manager\nJoin/leave rooms"]
        M["Message Router\nRoute messages"]
        U["User Manager\nTrack users"]
    end
    subgraph Storage Layer
        DB[(Database)]
        Cache[(Redis)]
    end
    C -->|TCP| A
    A -->|Assign| W
    W -->|Join| R
    W -->|Send| M
    M -->|Broadcast| W
    R -->|Persist| DB
    M -->|Cache| Cache

Key Patterns

PatternPurpose
Acceptor-WorkerSeparate connection acceptance from I/O handling
Room ManagementGroup users for targeted broadcasting
Message RoutingEfficient message delivery to subscribers
Connection PoolingReuse connections, limit resources

2. Boost.Asio Async I/O

Basic Server

#include <boost/asio.hpp>
#include <iostream>
#include <memory>
using boost::asio::ip::tcp;
class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket) : socket_(std::move(socket)) {}
    
    void start() {
        do_read();
    }
private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(
            boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    do_write(length);
                }
            });
    }
    
    void do_write(std::size_t length) {
        auto self(shared_from_this());
        boost::asio::async_write(
            socket_,
            boost::asio::buffer(data_, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    do_read();
                }
            });
    }
    
    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};
class Server {
public:
    Server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        do_accept();
    }
private:
    void do_accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<Session>(std::move(socket))->start();
                }
                do_accept();
            });
    }
    
    tcp::acceptor acceptor_;
};
int main() {
    try {
        boost::asio::io_context io_context;
        Server server(io_context, 8080);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
}

Key: Async I/O with callbacks—no blocking, handles thousands of connections.

3. Acceptor-Worker Pattern

Acceptor: Accept Connections

class Acceptor {
public:
    Acceptor(boost::asio::io_context& io_context, short port, WorkerPool& pool)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
          worker_pool_(pool) {
        do_accept();
    }
private:
    void do_accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    worker_pool_.assign(std::move(socket));
                }
                do_accept();
            });
    }
    
    tcp::acceptor acceptor_;
    WorkerPool& worker_pool_;
};

Worker Pool: Handle I/O

class WorkerPool {
public:
    WorkerPool(std::size_t thread_count) {
        for (std::size_t i = 0; i < thread_count; ++i) {
            io_contexts_.emplace_back(std::make_unique<boost::asio::io_context>());
            threads_.emplace_back([this, i] {
                io_contexts_[i]->run();
            });
        }
    }
    
    void assign(tcp::socket socket) {
        auto& io_context = *io_contexts_[next_io_context_];
        next_io_context_ = (next_io_context_ + 1) % io_contexts_.size();
        
        std::make_shared<Session>(std::move(socket), io_context)->start();
    }
    
    void stop() {
        for (auto& io_context : io_contexts_) {
            io_context->stop();
        }
        for (auto& thread : threads_) {
            thread.join();
        }
    }
private:
    std::vector<std::unique_ptr<boost::asio::io_context>> io_contexts_;
    std::vector<std::thread> threads_;
    std::size_t next_io_context_ = 0;
};

Key: Acceptor assigns connections to worker threads in round-robin fashion.

4. Room Management

Room Class

class Room {
public:
    void join(std::shared_ptr<Session> session) {
        std::lock_guard<std::mutex> lock(mutex_);
        sessions_.insert(session);
    }
    
    void leave(std::shared_ptr<Session> session) {
        std::lock_guard<std::mutex> lock(mutex_);
        sessions_.erase(session);
    }
    
    void broadcast(const std::string& message, std::shared_ptr<Session> sender) {
        std::lock_guard<std::mutex> lock(mutex_);
        for (auto& session : sessions_) {
            if (session != sender) {
                session->send(message);
            }
        }
    }
    
    std::size_t size() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return sessions_.size();
    }
private:
    std::set<std::shared_ptr<Session>> sessions_;
    mutable std::mutex mutex_;
};

Room Manager

class RoomManager {
public:
    std::shared_ptr<Room> get_or_create(const std::string& room_id) {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = rooms_.find(room_id);
        if (it != rooms_.end()) {
            return it->second;
        }
        auto room = std::make_shared<Room>();
        rooms_[room_id] = room;
        return room;
    }
    
    void remove_if_empty(const std::string& room_id) {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = rooms_.find(room_id);
        if (it != rooms_.end() && it->second->size() == 0) {
            rooms_.erase(it);
        }
    }
private:
    std::unordered_map<std::string, std::shared_ptr<Room>> rooms_;
    std::mutex mutex_;
};

Key: Thread-safe room management with automatic cleanup.

5. Message Routing

Message Router

struct Message {
    std::string type;      // "join", "leave", "message"
    std::string room_id;
    std::string user_id;
    std::string content;
    std::chrono::system_clock::time_point timestamp;
};
class MessageRouter {
public:
    void route(const Message& msg) {
        if (msg.type == "join") {
            handle_join(msg);
        } else if (msg.type == "leave") {
            handle_leave(msg);
        } else if (msg.type == "message") {
            handle_message(msg);
        }
    }
private:
    void handle_join(const Message& msg) {
        auto room = room_manager_.get_or_create(msg.room_id);
        auto session = session_manager_.get(msg.user_id);
        room->join(session);
        
        // Broadcast join notification
        room->broadcast(msg.user_id + " joined", session);
    }
    
    void handle_leave(const Message& msg) {
        auto room = room_manager_.get_or_create(msg.room_id);
        auto session = session_manager_.get(msg.user_id);
        room->leave(session);
        room_manager_.remove_if_empty(msg.room_id);
        
        // Broadcast leave notification
        room->broadcast(msg.user_id + " left", session);
    }
    
    void handle_message(const Message& msg) {
        auto room = room_manager_.get_or_create(msg.room_id);
        auto session = session_manager_.get(msg.user_id);
        
        // Broadcast message to room
        room->broadcast(msg.content, session);
    }
    
    RoomManager room_manager_;
    SessionManager session_manager_;
};

Key: Centralized message routing with type-based dispatch.

6. Connection Pooling

Connection Pool

class ConnectionPool {
public:
    ConnectionPool(std::size_t max_connections)
        : max_connections_(max_connections) {}
    
    bool try_acquire(const std::string& user_id) {
        std::lock_guard<std::mutex> lock(mutex_);
        if (active_connections_.size() >= max_connections_) {
            return false;
        }
        active_connections_.insert(user_id);
        return true;
    }
    
    void release(const std::string& user_id) {
        std::lock_guard<std::mutex> lock(mutex_);
        active_connections_.erase(user_id);
    }
    
    std::size_t active_count() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return active_connections_.size();
    }
private:
    std::size_t max_connections_;
    std::set<std::string> active_connections_;
    mutable std::mutex mutex_;
};

Key: Limit concurrent connections to prevent resource exhaustion.

7. Heartbeat & Timeout

Heartbeat Timer

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket, boost::asio::io_context& io_context)
        : socket_(std::move(socket)),
          heartbeat_timer_(io_context),
          timeout_timer_(io_context) {}
    
    void start() {
        start_heartbeat();
        start_timeout();
        do_read();
    }
private:
    void start_heartbeat() {
        heartbeat_timer_.expires_after(std::chrono::seconds(30));
        heartbeat_timer_.async_wait([this, self = shared_from_this()](boost::system::error_code ec) {
            if (!ec) {
                send_heartbeat();
                start_heartbeat();
            }
        });
    }
    
    void start_timeout() {
        timeout_timer_.expires_after(std::chrono::seconds(60));
        timeout_timer_.async_wait([this, self = shared_from_this()](boost::system::error_code ec) {
            if (!ec) {
                std::cout << "Client timeout\n";
                socket_.close();
            }
        });
    }
    
    void reset_timeout() {
        timeout_timer_.cancel();
        start_timeout();
    }
    
    void send_heartbeat() {
        send("PING");
    }
    
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(
            boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    reset_timeout();  // Reset on activity
                    process_message(std::string(data_, length));
                    do_read();
                }
            });
    }
    
    tcp::socket socket_;
    boost::asio::steady_timer heartbeat_timer_;
    boost::asio::steady_timer timeout_timer_;
    enum { max_length = 1024 };
    char data_[max_length];
};

Key: Heartbeat keeps connection alive; timeout detects idle clients.

8. Graceful Shutdown

Shutdown Handler

class Server {
public:
    void start() {
        setup_signal_handlers();
        io_context_.run();
    }
    
    void stop() {
        std::cout << "Shutting down...\n";
        
        // Stop accepting new connections
        acceptor_.close();
        
        // Notify all sessions
        for (auto& session : sessions_) {
            session->send("SERVER_SHUTDOWN");
        }
        
        // Wait for sessions to close
        std::this_thread::sleep_for(std::chrono::seconds(5));
        
        // Force close remaining sessions
        for (auto& session : sessions_) {
            session->close();
        }
        
        // Stop worker pool
        worker_pool_.stop();
        
        // Stop io_context
        io_context_.stop();
    }
private:
    void setup_signal_handlers() {
        signals_.async_wait([this](boost::system::error_code /*ec*/, int /*signo*/) {
            stop();
        });
    }
    
    boost::asio::io_context io_context_;
    boost::asio::signal_set signals_{io_context_, SIGINT, SIGTERM};
    tcp::acceptor acceptor_;
    WorkerPool worker_pool_;
    std::vector<std::shared_ptr<Session>> sessions_;
};

Key: Graceful shutdown: stop accepting, notify clients, wait, force close.

9. Production Patterns

Pattern 1: Message Queue Integration

class MessageQueue {
public:
    void publish(const std::string& channel, const Message& msg) {
        // Redis Pub/Sub
        redis_client_.publish(channel, serialize(msg));
    }
    
    void subscribe(const std::string& channel, std::function<void(const Message&)> callback) {
        redis_client_.subscribe(channel, [callback](const std::string& data) {
            callback(deserialize(data));
        });
    }
};
// Use in distributed chat server
void handle_message(const Message& msg) {
    // Publish to Redis
    message_queue_.publish("chat:" + msg.room_id, msg);
}
// Subscribe on all servers
message_queue_.subscribe("chat:*", [](const Message& msg) {
    // Broadcast to local connections
    local_room_manager_.broadcast(msg);
});

Key: Redis Pub/Sub for horizontal scaling across multiple servers.

Pattern 2: Rate Limiting

class RateLimiter {
public:
    bool allow(const std::string& user_id) {
        auto now = std::chrono::steady_clock::now();
        std::lock_guard<std::mutex> lock(mutex_);
        
        auto& bucket = buckets_[user_id];
        
        // Refill tokens
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - bucket.last_refill).count();
        bucket.tokens = std::min(bucket.tokens + elapsed * refill_rate_, max_tokens_);
        bucket.last_refill = now;
        
        // Check if tokens available
        if (bucket.tokens > 0) {
            --bucket.tokens;
            return true;
        }
        return false;
    }
private:
    struct Bucket {
        int tokens = max_tokens_;
        std::chrono::steady_clock::time_point last_refill = std::chrono::steady_clock::now();
    };
    
    std::unordered_map<std::string, Bucket> buckets_;
    std::mutex mutex_;
    static constexpr int max_tokens_ = 10;
    static constexpr int refill_rate_ = 1;  // tokens per second
};

Key: Token bucket rate limiting per user.

Pattern 3: Message Persistence

class MessageStore {
public:
    void save(const Message& msg) {
        // PostgreSQL
        pqxx::connection conn("dbname=chat user=postgres");
        pqxx::work txn(conn);
        
        txn.exec_params(
            "INSERT INTO messages (room_id, user_id, content, timestamp) VALUES ($1, $2, $3, $4)",
            msg.room_id, msg.user_id, msg.content, msg.timestamp
        );
        
        txn.commit();
    }
    
    std::vector<Message> get_history(const std::string& room_id, int limit = 100) {
        pqxx::connection conn("dbname=chat user=postgres");
        pqxx::work txn(conn);
        
        auto result = txn.exec_params(
            "SELECT * FROM messages WHERE room_id = $1 ORDER BY timestamp DESC LIMIT $2",
            room_id, limit
        );
        
        std::vector<Message> messages;
        for (const auto& row : result) {
            messages.push_back(parse_message(row));
        }
        return messages;
    }
};

Key: Persist messages to database for history.

10. Complete Example

// Full production chat server (simplified)
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <set>
#include <unordered_map>
#include <mutex>
using boost::asio::ip::tcp;
class Session;
class Room;
class RoomManager;
class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket, RoomManager& room_manager)
        : socket_(std::move(socket)), room_manager_(room_manager) {}
    
    void start() {
        do_read();
    }
    
    void send(const std::string& message) {
        bool write_in_progress = !write_queue_.empty();
        write_queue_.push_back(message);
        if (!write_in_progress) {
            do_write();
        }
    }
private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(
            boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    std::string msg(data_, length);
                    process_message(msg);
                    do_read();
                } else {
                    leave_all_rooms();
                }
            });
    }
    
    void do_write() {
        auto self(shared_from_this());
        boost::asio::async_write(
            socket_,
            boost::asio::buffer(write_queue_.front()),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    write_queue_.pop_front();
                    if (!write_queue_.empty()) {
                        do_write();
                    }
                } else {
                    leave_all_rooms();
                }
            });
    }
    
    void process_message(const std::string& msg);
    void leave_all_rooms();
    
    tcp::socket socket_;
    RoomManager& room_manager_;
    enum { max_length = 1024 };
    char data_[max_length];
    std::deque<std::string> write_queue_;
    std::set<std::string> joined_rooms_;
};
class Room {
public:
    void join(std::shared_ptr<Session> session) {
        sessions_.insert(session);
    }
    
    void leave(std::shared_ptr<Session> session) {
        sessions_.erase(session);
    }
    
    void broadcast(const std::string& message, std::shared_ptr<Session> sender) {
        for (auto& session : sessions_) {
            if (session != sender) {
                session->send(message);
            }
        }
    }
private:
    std::set<std::shared_ptr<Session>> sessions_;
};
class RoomManager {
public:
    std::shared_ptr<Room> get_or_create(const std::string& room_id) {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = rooms_.find(room_id);
        if (it != rooms_.end()) {
            return it->second;
        }
        auto room = std::make_shared<Room>();
        rooms_[room_id] = room;
        return room;
    }
private:
    std::unordered_map<std::string, std::shared_ptr<Room>> rooms_;
    std::mutex mutex_;
};
void Session::process_message(const std::string& msg) {
    // Parse: "JOIN room_id" or "MSG room_id content"
    if (msg.substr(0, 4) == "JOIN") {
        std::string room_id = msg.substr(5);
        auto room = room_manager_.get_or_create(room_id);
        room->join(shared_from_this());
        joined_rooms_.insert(room_id);
    } else if (msg.substr(0, 3) == "MSG") {
        auto space_pos = msg.find(' ', 4);
        std::string room_id = msg.substr(4, space_pos - 4);
        std::string content = msg.substr(space_pos + 1);
        auto room = room_manager_.get_or_create(room_id);
        room->broadcast(content, shared_from_this());
    }
}
void Session::leave_all_rooms() {
    for (const auto& room_id : joined_rooms_) {
        auto room = room_manager_.get_or_create(room_id);
        room->leave(shared_from_this());
    }
    joined_rooms_.clear();
}
class Server {
public:
    Server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        do_accept();
    }
private:
    void do_accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<Session>(std::move(socket), room_manager_)->start();
                }
                do_accept();
            });
    }
    
    tcp::acceptor acceptor_;
    RoomManager room_manager_;
};
int main() {
    try {
        boost::asio::io_context io_context;
        Server server(io_context, 8080);
        std::cout << "Chat server listening on port 8080\n";
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
}

Key: Production-ready chat server with room management and message routing.

Summary

Key Components

ComponentPurpose
Boost.AsioAsync I/O framework
Acceptor-WorkerSeparate accept from I/O handling
Room ManagerGroup users for broadcasting
Message RouterRoute messages to subscribers
Connection PoolLimit concurrent connections
HeartbeatDetect idle clients
Chat servers require async I/O, room management, message routing, and scalability patterns for production use.

Keywords

C++ chat server, Boost.Asio, async I/O, room management, message routing, connection pooling, real-time messaging One-line summary: Build production C++ chat servers with Boost.Asio async I/O, acceptor-worker pattern, room management, message routing, and connection pooling for scalable real-time messaging.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Build production C++ chat servers: Boost.Asio async I/O, acceptor-worker pattern, room management, message routing, conn… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, chat-server, Boost.Asio, networking, async, real-time, architecture 등으로 검색하시면 이 글이 도움이 됩니다.