A transport-agnostic, high-performance C++23 WebSocket client library with minimal dependencies.
- Full RFC 6455 compliance
- WebSocket Secure (WSS) support
- Compression support (permessage-deflate protocol extension, RFC 7692)
- Support for
zlib-ng
overzlib
library for improved deflate performance on modern architectures - Fast UTF-8 validation using SIMD (simdutf, optional)
- Fast payload masking using SIMD
- Does not throw exceptions (works with
-fno-exceptions
) - No hidden networking control flow
- User decides when and what to write as response to ping/pong/close frames
- Few dependencies (STL, OpenSSL, zlib, simdutf)
- Pluggable transport layer
- Blocking I/O support (built-in)
- Non-blocking I/O support using C++20 coroutines, e.g. using standalone ASIO
- No callback hell and easier object liftime management using C++20 coroutines
- Pluggable logging
- GCC compiler support, C++23 required (TODO Clang)
- Tested on 64-bit x86 and ARM64 (Ubuntu x86, MacOS M2 ARM64) platforms (32-bit NOT supported)
- Passes all Autobahn Testsuite tests
NOTE: Despite being used in production, this library is still under development and the API may change.
Dependency | Description | Required | Switch |
---|---|---|---|
simdutf | SIMD instructions based UTF-8 validator used for TEXT messages payload validation. | Optional | WS_CLIENT_USE_SIMD_UTF8 |
openssl 3+ | WebSocket Secure (WSS) support. | Optional | |
zlib | Message compression support through permessage-deflate extension. | Optional | WS_CLIENT_USE_ZLIB_NG=0 |
zlib-ng | Faster alternative to zlib library with optimizations for modern CPUs. |
Optional | WS_CLIENT_USE_ZLIB_NG=1 |
Working examples can be found in the examples directory.
The library is designed to be transport layer agnostic. The library supports both synchronous and asynchronous transport layers. Built-in blocking I/O transport layers are provided, incl. bindings for C++20 coroutines using standalone ASIO (no Boost dependency).
The user can provide their own transport layer implementation if needed.
- Synchronous example: examples/ex_echo_sync.cpp
- ASIO example: examples/ex_echo_asio.cpp
By default, the library logs directly to std::clog
, hence there is no dependency to any logging library.
The default implementation allows to set the log level at compile-time, which can be used to filter log messages.
ConsoleLogger<LogLevel::I> logger;
auto client = WebSocketClient(&logger, [...]);
In this example, only log messages with log level I
(info) and higher will be printed.
The available log levels are:
enum class LogLevel : uint8_t
{
N = 0, // Disabled
E = 1, // Error
W = 2, // Warning
I = 3, // Info
D = 4 // Debug
};
You can implement a custom logger like the following:
struct CustomLogger
{
/**
* Check if the logger is enabled for the given log level.
*/
template <LogLevel level>
constexpr bool is_enabled() const noexcept
{
return true;
}
/**
* Log a message with the given log level.
*/
template <LogLevel level>
constexpr void log(
std::string_view message, const std::source_location loc = std::source_location::current()
) noexcept
{
std::cout << "CustomLogger: " << loc.file_name() << ":" << loc.line() << " " << message
<< std::endl;
}
};
Sometimes, changing the log-level will either show too many messages, or hide the ones of interest.
In order to filter for specific implementation details, the following macro-switches are available (0
= disabled, 1
= enabled):
#define WS_CLIENT_LOG_HANDSHAKE 0
#define WS_CLIENT_LOG_MSG_PAYLOADS 0
#define WS_CLIENT_LOG_MSG_SIZES 0
#define WS_CLIENT_LOG_FRAMES 0
#define WS_CLIENT_LOG_COMPRESSION 0
By setting a variable to 0
= disabled (1
= enabled), the compiler will optimize out all logging code for maximum performance.
For example, the handshake log messages are useful to inspect the HTTP headers sent and received during the WebSocket handshake. Among others, the negotiated parameters for the permessage-deflate compression extension can be inspected this way.
Template type parameters are supplemented by C++23 concepts, which are used to validate template parameters at compile-time. Concepts have the advantage to formalize requirements for a template parameter, similar to interface definitions, and provide more meaningful error messages.
This client implementation is not thread-aware, hence does not employ any synchronization primitives. If used in a multi-threaded environment, synchronization needs to be conducted by the user.
The control frames ping, pong and close are returned to the client in the same order as they are received. The user is responsible for sending the corresponding pong or close frame in response. By returning those frames to the user, the library enables the user to decide when and what to write as response, and does not hide any networking control flow, which would require synchronization.
The implementation does not allocate separate memory for each message and/or frames.
WebSocketClient
maintains a configurable read buffer, which are reused for all messages and frames.
On a write operation, the message payload is directly written to the socket, without copying it to a separate buffer.
Additionally, if enabled, the PermessageDeflate
compression extension maintains a compression and decompression buffer, which are used for all messages and frames.
This implies that the maximum message size is limited by the size of the read/write/compression buffers.
If exceeded, a BUFFER_ERROR
error will be returned.
Received Message
objects must be processed immediately after receiving them, otherwise the next message will overwrite the payload.
Message
objects must not be stored for later processing. If delayed processing is required, the payload must be copied away to a user-defined buffer.
Pull requests or issues are welcome, see CONTRIBUTE.md.
The library passes all tests of the Autobahn Testsuite, see Autobahn Testsuite report.
Distributed under the MIT license, see LICENSE.