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
- Architecture Overview
- Boost.Asio Async I/O
- Acceptor-Worker Pattern
- Room Management
- Message Routing
- Connection Pooling
- Heartbeat & Timeout
- Graceful Shutdown
- Production Patterns
- 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
| Pattern | Purpose |
|---|---|
| Acceptor-Worker | Separate connection acceptance from I/O handling |
| Room Management | Group users for targeted broadcasting |
| Message Routing | Efficient message delivery to subscribers |
| Connection Pooling | Reuse 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
| Component | Purpose |
|---|---|
| Boost.Asio | Async I/O framework |
| Acceptor-Worker | Separate accept from I/O handling |
| Room Manager | Group users for broadcasting |
| Message Router | Route messages to subscribers |
| Connection Pool | Limit concurrent connections |
| Heartbeat | Detect 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++ HTTP 기초 완벽 가이드 | 요청/응답 파싱·헤더·청크 인코딩·Beast 실전 [#30-1]
- C++ REST API 서버 만들기 | 라우팅·미들웨어·인증·Swagger 문서화 [#50-2]
- C++ Lock-Free 프로그래밍 실전 | CAS·ABA·메모리 순서·고성능 큐 [#34-3]
이 글에서 다루는 키워드 (관련 검색어)
C++, chat-server, Boost.Asio, networking, async, real-time, architecture 등으로 검색하시면 이 글이 도움이 됩니다.