WebSocket in C++: Building High-Performance Real-Time Applications
In the modern world of web development, real-time interaction has become the de facto standard for financial platforms, online games, monitoring systems, and chats. If you need maximum performance and minimal latency, C++ remains the only choice. In this article, we will explore advanced techniques for working with WebSocket in C++, going beyond a simple echo server.
We will focus on architectural solutions that allow handling tens of thousands of simultaneous connections and analyze specific code examples using popular libraries.
1. Choosing a Library and Project Setup
For production development in C++, there are two main approaches: using the high-level library uWebSockets (uWS) or the low-level combination of Boost.Asio + Boost.Beast. The first option is simpler, the second gives full control.
Boost.Beast (based on Asio) is the industry standard for projects where every microsecond matters. Let's look at the basic structure of an asynchronous WebSocket server:
#include <boost/beast/core.hpp>#include <boost/beast/websocket.hpp>#include <boost/asio.hpp>#include <memory>#include <string>
namespace beast = boost::beast;namespace websocket = beast::websocket;namespace net = boost::asio;using tcp = net::ip::tcp;
// Class for handling a single connectionclass WebSocketSession : public std::enable_shared_from_this<WebSocketSession> { websocket::stream<beast::tcp_stream> ws_; beast::flat_buffer buffer_;public: explicit WebSocketSession(tcp::socket&& socket) : ws_(std::move(socket)) {}
void run() { // Asynchronously accept the WebSocket handshake ws_.async_accept( beast::bind_front_handler(&WebSocketSession::on_accept, shared_from_this())); }
void on_accept(beast::error_code ec) { if(!ec) { // Send a welcome message ws_.async_write( net::buffer(std::string("Welcome to the real-time world!")), beast::bind_front_handler(&WebSocketSession::on_write, shared_from_this())); do_read(); } } // ... (continued below)};The key point: each client gets its own instance of WebSocketSession, operating in asynchronous mode. This allows not blocking the main thread while waiting for data.
2. Multithreaded Server Architecture with a Thread Pool
A single io_context thread hits the CPU limit. To scale to thousands of connections, use a thread pool. Each thread runs its own instance of io_context, and connections are distributed via accept balancing.
void run_server(const std::string& address, unsigned short port, int thread_count) { net::io_context ioc{thread_count}; // Create io_context specifying the number of threads
tcp::acceptor acceptor{ioc, {net::ip::make_address(address), port}};
// Start accept in a loop std::function<void()> do_accept = [&]() { acceptor.async_accept( [&](beast::error_code ec, tcp::socket socket) { if (!ec) { // Create a session and pass it the socket std::make_shared<WebSocketSession>(std::move(socket))->run(); } do_accept(); // Continue accepting }); }; do_accept();
// Start the thread pool std::vector<std::thread> threads; for (int i = 0; i < thread_count; ++i) { threads.emplace_back([&ioc]() { ioc.run(); }); }
for (auto& t : threads) t.join();}Important