C++ Chat Server Architecture: Boost.Asio, Room Management,
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.
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Build production C++ chat servers: Boost.Asio async I/O, acceptor-worker pattern, room management, message routing, conn…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ HTTP 기초 완벽 가이드 | 요청/응답 파싱·헤더·청크 인코딩·Beast 실전 [#30-1]
- C++ REST API 서버 만들기 | 라우팅·미들웨어·인증·Swagger 문서화 [#50-2]
- C++ Lock-Free 프로그래밍 실전 | CAS·ABA·메모리 순서·고성능 큐 [#34-3]
Keywords Covered in This Article (Related Search Terms)
This article covers C++, chat-server, Boost.Asio, networking, async, real-time, architecture.