C++ WebSocket Guide
C++ WebSocket implementations offer high performance and low latency for real-time applications. This guide covers the two most popular libraries for building WebSocket servers and clients in C++: Boost.Beast and websocketpp. Both libraries provide robust implementations of the WebSocket protocol, with Boost.Beast being the modern recommended choice for new projects due to its integration with Boost.Asio and active maintenance.
WebSocket communication in C++ requires handling the WebSocket handshake, managing persistent connections, and processing binary or text frames efficiently. The libraries covered here handle protocol details while giving you control over connection management, threading models, and performance optimization.
Boost.Beast WebSocket Implementation
Boost.Beast is a header-only library built on top of Boost.Asio that provides HTTP and WebSocket functionality. It offers modern C++ design with zero-copy networking, full control over asynchronous operations, and excellent performance characteristics. Beast is part of the Boost libraries since version 1.66 and is actively maintained.
The boost beast websocket module provides both synchronous and asynchronous APIs, making it suitable for applications ranging from simple tools to high-performance servers handling thousands of concurrent connections.
CMake Setup for Boost.Beast
Setting up a Boost.Beast project requires Boost libraries version 1.66 or higher. Here is a CMake configuration that pulls in the necessary dependencies:
cmake_minimum_required(VERSION 3.15)
project(WebSocketServer CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Boost 1.66 REQUIRED COMPONENTS system thread)
find_package(Threads REQUIRED)
add_executable(websocket_server server.cpp)
target_link_libraries(websocket_server
Boost::system
Boost::thread
Threads::Threads
)
add_executable(websocket_client client.cpp)
target_link_libraries(websocket_client
Boost::system
Boost::thread
Threads::Threads
)
Install Boost using your package manager or build from source. On Ubuntu or Debian systems, use sudo apt-get install libboost-all-dev. On macOS with Homebrew, use brew install boost.
Boost.Beast WebSocket Echo Server
This example demonstrates an asynchronous WebSocket echo server using boost beast. The server accepts connections, performs the WebSocket handshake, and echoes messages back to clients:
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <iostream>
#include <memory>
#include <string>
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session> {
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
public:
explicit session(tcp::socket socket)
: ws_(std::move(socket)) {}
void run() {
net::dispatch(ws_.get_executor(),
beast::bind_front_handler(&session::on_run, shared_from_this()));
}
void on_run() {
ws_.set_option(websocket::stream_base::timeout::suggested(
beast::role_type::server));
ws_.set_option(websocket::stream_base::decorator(
[](websocket::response_type& res) {
res.set(http::field::server, "Boost.Beast WebSocket Server");
}));
ws_.async_accept(
beast::bind_front_handler(&session::on_accept, shared_from_this()));
}
void on_accept(beast::error_code ec) {
if (ec) {
std::cerr << "Accept error: " << ec.message() << std::endl;
return;
}
do_read();
}
void do_read() {
ws_.async_read(buffer_,
beast::bind_front_handler(&session::on_read, shared_from_this()));
}
void on_read(beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec == websocket::error::closed)
return;
if (ec) {
std::cerr << "Read error: " << ec.message() << std::endl;
return;
}
ws_.text(ws_.got_text());
ws_.async_write(buffer_.data(),
beast::bind_front_handler(&session::on_write, shared_from_this()));
}
void on_write(beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) {
std::cerr << "Write error: " << ec.message() << std::endl;
return;
}
buffer_.consume(buffer_.size());
do_read();
}
};
class listener : public std::enable_shared_from_this<listener> {
net::io_context& ioc_;
tcp::acceptor acceptor_;
public:
listener(net::io_context& ioc, tcp::endpoint endpoint)
: ioc_(ioc), acceptor_(ioc) {
beast::error_code ec;
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
std::cerr << "Open error: " << ec.message() << std::endl;
return;
}
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if (ec) {
std::cerr << "Set option error: " << ec.message() << std::endl;
return;
}
acceptor_.bind(endpoint, ec);
if (ec) {
std::cerr << "Bind error: " << ec.message() << std::endl;
return;
}
acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) {
std::cerr << "Listen error: " << ec.message() << std::endl;
return;
}
}
void run() {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
net::make_strand(ioc_),
beast::bind_front_handler(&listener::on_accept, shared_from_this()));
}
void on_accept(beast::error_code ec, tcp::socket socket) {
if (ec) {
std::cerr << "Accept error: " << ec.message() << std::endl;
} else {
std::make_shared<session>(std::move(socket))->run();
}
do_accept();
}
};
int main() {
auto const address = net::ip::make_address("0.0.0.0");
auto const port = static_cast<unsigned short>(8080);
net::io_context ioc{1};
std::make_shared<listener>(ioc, tcp::endpoint{address, port})->run();
std::cout << "WebSocket server running on ws://localhost:8080" << std::endl;
ioc.run();
return 0;
}
This server uses asynchronous I/O with completion handlers. Each connection gets its own session object that manages the WebSocket stream lifecycle. The flat_buffer provides efficient memory management for incoming messages.
Boost.Beast WebSocket Client
Building a boost websocket client follows a similar pattern but initiates the handshake instead of accepting it:
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <string>
namespace beast = boost::beast;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;
int main() {
try {
auto const host = "localhost";
auto const port = "8080";
auto const text = "Hello, WebSocket!";
net::io_context ioc;
tcp::resolver resolver{ioc};
websocket::stream<tcp::socket> ws{ioc};
auto const results = resolver.resolve(host, port);
auto ep = net::connect(ws.next_layer(), results);
std::string host_port = host + std::string(":") + std::to_string(ep.port());
ws.handshake(host_port, "/");
std::cout << "Connected to server" << std::endl;
ws.write(net::buffer(std::string(text)));
std::cout << "Sent: " << text << std::endl;
beast::flat_buffer buffer;
ws.read(buffer);
std::cout << "Received: " << beast::make_printable(buffer.data()) << std::endl;
ws.close(websocket::close_code::normal);
}
catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
This synchronous client connects to a server, sends a message, waits for a response, and closes the connection. For production applications, use the asynchronous API to avoid blocking operations.
WebSocketpp Library
websocketpp (also known as websocket++) is a header-only C++ library that provides WebSocket server and client functionality. While older than Boost.Beast, it remains popular and includes built-in support for various transport layers and is compatible with both Boost.Asio and standalone Asio.
The library uses a policy-based design allowing customization of transport, concurrency, logging, and random number generation. This makes websocketpp flexible but requires more configuration than Boost.Beast.
WebSocketpp CMake Setup
Here is a CMake configuration for websocketpp projects:
cmake_minimum_required(VERSION 3.15)
project(WebSocketppServer CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Boost REQUIRED COMPONENTS system thread)
find_package(Threads REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(/path/to/websocketpp)
add_executable(wpp_server server.cpp)
target_link_libraries(wpp_server
${Boost_LIBRARIES}
Threads::Threads
)
Install websocketpp from GitHub or use a package manager. On Ubuntu, use sudo apt-get install libwebsocketpp-dev. Clone the repository with git clone https://github.com/zaphoyd/websocketpp.git and update the include path accordingly.
WebSocketpp Echo Server Example
This websocketpp server demonstrates the callback-based API:
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <iostream>
#include <set>
typedef websocketpp::server<websocketpp::config::asio> server;
using websocketpp::connection_hdl;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
class echo_server {
public:
echo_server() {
m_server.init_asio();
m_server.set_open_handler(bind(&echo_server::on_open, this, _1));
m_server.set_close_handler(bind(&echo_server::on_close, this, _1));
m_server.set_message_handler(bind(&echo_server::on_message, this, _1, _2));
}
void on_open(connection_hdl hdl) {
m_connections.insert(hdl);
std::cout << "Connection opened. Total connections: "
<< m_connections.size() << std::endl;
}
void on_close(connection_hdl hdl) {
m_connections.erase(hdl);
std::cout << "Connection closed. Total connections: "
<< m_connections.size() << std::endl;
}
void on_message(connection_hdl hdl, server::message_ptr msg) {
std::cout << "Received message: " << msg->get_payload() << std::endl;
try {
m_server.send(hdl, msg->get_payload(), msg->get_opcode());
} catch (websocketpp::exception const & e) {
std::cout << "Echo failed: " << e.what() << std::endl;
}
}
void run(uint16_t port) {
m_server.listen(port);
m_server.start_accept();
std::cout << "WebSocket server running on port " << port << std::endl;
m_server.run();
}
private:
server m_server;
std::set<connection_hdl, std::owner_less<connection_hdl>> m_connections;
};
int main() {
echo_server server;
server.run(9002);
return 0;
}
The websocketpp design uses callbacks for connection lifecycle events. Connection handles (connection_hdl) are lightweight references that you can store and use to send messages later. This pattern works well for broadcast scenarios where you need to maintain a list of active connections.
Qt WebSocket Library
Qt provides WebSocket support through the Qt WebSockets module, which is ideal if your application already uses the Qt framework. The Qt implementation integrates seamlessly with Qt’s signal-slot mechanism and event loop:
#include <QCoreApplication>
#include <QWebSocketServer>
#include <QWebSocket>
QWebSocketServer *server = new QWebSocketServer(
"Echo Server",
QWebSocketServer::NonSecureMode
);
if (server->listen(QHostAddress::Any, 1234)) {
QObject::connect(server, &QWebSocketServer::newConnection, [&]() {
QWebSocket *socket = server->nextPendingConnection();
QObject::connect(socket, &QWebSocket::textMessageReceived,
[socket](QString message) {
socket->sendTextMessage(message);
});
});
}
Qt websockets are particularly useful for desktop applications or embedded systems already using Qt. The library handles threading automatically through Qt’s event loop, making it easier to integrate with GUI applications.
Libwebsocket Alternative
libwebsocket is a C library that provides WebSocket client and server implementations with minimal dependencies. While written in C, it can be used from C++ projects. The library focuses on embedded systems and resource-constrained environments:
#include <libwebsockets.h>
static int callback_echo(struct lws *wsi,
enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_RECEIVE:
lws_write(wsi, (unsigned char *)in, len, LWS_WRITE_TEXT);
break;
default:
break;
}
return 0;
}
static struct lws_protocols protocols[] = {
{ "echo-protocol", callback_echo, 0, 1024 },
{ NULL, NULL, 0, 0 }
};
libwebsocket is lightweight and suitable for IoT devices, but it requires manual memory management and has a steeper learning curve compared to modern C++ alternatives.
TLS and Secure WebSockets (WSS)
Production applications require secure WebSocket connections using TLS. Both Boost.Beast and websocketpp support WSS through OpenSSL integration.
For Boost.Beast, use websocket::stream<beast::ssl_stream<beast::tcp_stream>> instead of the plain TCP stream:
#include <boost/beast/ssl.hpp>
#include <boost/asio/ssl.hpp>
namespace ssl = boost::asio::ssl;
ssl::context ctx{ssl::context::tlsv12};
ctx.set_options(
ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::no_sslv3);
ctx.use_certificate_chain_file("server.crt");
ctx.use_private_key_file("server.key", ssl::context::pem);
websocket::stream<beast::ssl_stream<tcp::socket>> wss{ioc, ctx};
For websocketpp, use the TLS-enabled config:
#include <websocketpp/config/asio_tls.hpp>
typedef websocketpp::server<websocketpp::config::asio_tls> tls_server;
server.set_tls_init_handler([](websocketpp::connection_hdl) {
auto ctx = websocketpp::lib::make_shared<asio::ssl::context>(
asio::ssl::context::tlsv12);
ctx->use_certificate_chain_file("server.crt");
ctx->use_private_key_file("server.key", asio::ssl::context::pem);
return ctx;
});
Use certificates from a trusted Certificate Authority for production deployments. For development, generate self-signed certificates with OpenSSL: openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes.
Performance Optimization
C++ web socket implementations can handle thousands of concurrent connections with proper optimization. Here are key performance considerations:
Use Asynchronous I/O: Synchronous operations block threads and limit scalability. Asynchronous APIs in Boost.Beast and websocketpp allow a single thread to manage many connections efficiently.
Thread Pool Configuration: For CPU-intensive processing, run the io_context with multiple threads. Boost.Asio distributes work across threads automatically:
net::io_context ioc{std::thread::hardware_concurrency()};
std::vector<std::thread> threads;
for (int i = 0; i < std::thread::hardware_concurrency(); ++i) {
threads.emplace_back([&ioc]() { ioc.run(); });
}
for (auto& t : threads) {
t.join();
}
Message Buffering: Reuse buffers instead of allocating new ones for each message. Boost.Beast’s flat_buffer provides efficient buffer management. For custom needs, implement a buffer pool to reduce allocator pressure.
Compression: Enable permessage-deflate extension to reduce bandwidth:
ws_.set_option(websocket::permessage_deflate(
websocket::permessage_deflate{
true, // client_enable
true, // server_enable
9, // compression level
15, // window bits
8 // mem level
}));
Keep-Alive Settings: Configure ping/pong timeouts to detect dead connections:
ws_.set_option(websocket::stream_base::timeout{
std::chrono::seconds(30), // handshake timeout
std::chrono::seconds(300), // idle timeout
true // keep alive pings
});
TCP Options: Enable TCP_NODELAY to reduce latency for small messages:
beast::get_lowest_layer(ws_).socket().set_option(tcp::no_delay(true));
Benchmark Your Application: Use tools like wrk or custom load testers to measure throughput and latency. Profile with perf, valgrind, or Visual Studio profiler to identify bottlenecks.
Testing WebSocket Applications
Testing c++ websocket implementations requires both unit tests and integration tests. For unit testing individual components, use frameworks like Google Test or Catch2. For integration testing, you need to verify the complete WebSocket flow including handshake, message exchange, and connection closure.
Use WebSocket testing tools to validate server behavior under various conditions. Test edge cases like connection drops, malformed frames, and large messages. Load testing with thousands of concurrent connections helps identify scalability limits.
Mock clients for testing server code:
TEST(WebSocketServer, EchoMessage) {
net::io_context ioc;
tcp::resolver resolver{ioc};
websocket::stream<tcp::socket> ws{ioc};
auto const results = resolver.resolve("localhost", "8080");
net::connect(ws.next_layer(), results);
ws.handshake("localhost:8080", "/");
std::string test_message = "test";
ws.write(net::buffer(test_message));
beast::flat_buffer buffer;
ws.read(buffer);
ASSERT_EQ(beast::buffers_to_string(buffer.data()), test_message);
}
Frequently Asked Questions
What is the difference between Boost.Beast and websocketpp?
Boost.Beast is the modern recommended choice for new projects. It is actively maintained as part of the official Boost libraries, has better documentation, and provides a cleaner API that follows modern C++ practices. websocketpp is mature and stable but has less active development. Beast also offers better integration with Boost.Asio and more flexibility for custom protocols.
Can I use boost beast websocket without the rest of Boost?
Boost.Beast requires several other Boost libraries including Boost.Asio, Boost.System, and header-only components like Boost.Core. While you cannot use Beast completely standalone, it is header-only itself and only requires linking against Boost.System. The total dependency footprint is reasonable for most projects.
How do I handle binary data with websocketpp or Boost.Beast?
Both libraries support binary frames. In Boost.Beast, check the frame type with ws_.got_binary() and set the binary flag before writing with ws_.binary(true). In websocketpp, use websocket::frame::opcode::BINARY when sending and check msg->get_opcode() when receiving. Binary data is handled as std::vector<uint8_t> or raw buffers.
Which library has better performance for high-concurrency scenarios?
Boost.Beast generally offers better performance due to its zero-copy design and tighter integration with Asio. Benchmarks show Beast can handle more concurrent connections with lower memory overhead. However, actual performance depends on your specific use case, message patterns, and implementation quality. Profile both libraries with your workload before making a decision based solely on performance claims.