WebSocket in C++: Erstellung leistungsstarker Echtzeitanwendungen
In der modernen Webentwicklung ist die Echtzeitinteraktion zum De-facto-Standard für Finanzplattformen, Online-Spiele, Überwachungssysteme und Chats geworden. Wenn Sie maximale Leistung und minimale Latenz benötigen, bleibt C++ die unangefochtene Wahl. In diesem Artikel betrachten wir fortgeschrittene Techniken für die Arbeit mit WebSocket in C++, die über einen einfachen Echo-Server hinausgehen.
Wir konzentrieren uns auf Architekturlösungen, die die Verarbeitung von Zehntausenden gleichzeitiger Verbindungen ermöglichen, und analysieren konkrete Codebeispiele unter Verwendung gängiger Bibliotheken.
1. Bibliotheksauswahl und Projekteinrichtung
Für die Produktionsentwicklung in C++ gibt es zwei Hauptansätze: die Verwendung der High-Level-Bibliothek uWebSockets (uWS) oder die Low-Level-Kombination Boost.Asio + Boost.Beast. Die erste Variante ist einfacher, die zweite bietet vollständige Kontrolle.
Boost.Beast (basierend auf Asio) ist der Industriestandard für Projekte, bei denen jede Mikrosekunde zählt. Betrachten wir die grundlegende Struktur eines asynchronen WebSocket-Servers:
#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;
// Klasse zur Verarbeitung einer einzelnen Verbindungclass 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() { // Asynchron den WebSocket-Handshake annehmen ws_.async_accept( beast::bind_front_handler(&WebSocketSession::on_accept, shared_from_this())); }
void on_accept(beast::error_code ec) { if(!ec) { // Begrüßungsnachricht senden ws_.async_write( net::buffer(std::string("Willkommen in der Echtzeit-Welt!")), beast::bind_front_handler(&WebSocketSession::on_write, shared_from_this())); do_read(); } } // ... (Fortsetzung unten)};Wichtiger Punkt: Jeder Client erhält eine eigene Instanz von WebSocketSession, die im asynchronen Modus arbeitet. Dadurch wird der Hauptthread beim Warten auf Daten nicht blockiert.
2. Architektur eines Multithread-Servers mit Thread-Pool
Ein einzelner io_context-Thread stößt an die CPU-Grenze. Für die Skalierung auf Tausende von Verbindungen verwenden Sie einen Thread-Pool. Jeder Thread startet seine eigene Instanz von io_context, und die Verbindungen werden durch Accept-Balancing verteilt.
void run_server(const std::string& address, unsigned short port, int thread_count) { net::io_context ioc{thread_count}; // io_context mit Angabe der Thread-Anzahl erstellen
tcp::acceptor acceptor{ioc, {net::ip::make_address(address), port}};
// Accept in einer Schleife starten std::function<void()> do_accept = [&]() { acceptor.async_accept( [&](beast::error_code ec, tcp::socket socket) { if (!ec) { // Session erstellen und Socket übergeben std::make_shared<WebSocketSession>(std::move(socket))->run(); } do_accept(); // Weiter annehmen }); }; do_accept();
// Thread-Pool starten 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();}Wichtig