...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Because calls to read data may return a variable amount of bytes, the interface
to calls that read data require an object that meets the requirements of
DynamicBuffer.
This concept is modeled on boost::asio::streambuf
.
The implementation does not perform queueing or buffering of messages. If desired, these features should be provided by callers. The impact of this design is that library users are in full control of the allocation strategy used to store data and the back-pressure applied on the read and write side of the underlying TCP/IP connection.
Asynchronous versions are available for all functions:
multi_buffer buffer; ws.async_read(buffer, [](error_code, std::size_t) { // Do something with the buffer });
Calls to asynchronous initiation functions support the extensible asynchronous model developed by the Boost.Asio author, allowing for traditional completion handlers, stackful or stackless coroutines, and even futures:
void echo(stream<boost::asio::ip::tcp::socket>& ws, multi_buffer& buffer, boost::asio::yield_context yield) { ws.async_read(buffer, yield); std::future<std::size_t> fut = ws.async_write(buffer.data(), boost::asio::use_future); }
The example programs that come with the library demonstrate the usage of websocket stream operations with all asynchronous varieties.
The creation and operation of the boost::asio::io_context
associated with the
underlying stream is left to the callers, permitting any implementation strategy
including one that does not require threads for environments where threads
are unavailable. Beast WebSocket itself does not use or require threads.
Like a regular Boost.Asio
socket, a stream
is not thread safe. Callers
are responsible for synchronizing operations on the socket using an implicit
or explicit strand, as per the Asio documentation. The websocket stream asynchronous
interface supports one of each of the following operations to be active at
the same time:
For example, the following code is malformed, because the program is attempting to perform two simultaneous reads:
ws.async_read(b, [](error_code, std::size_t){}); ws.async_read(b, [](error_code, std::size_t){});
However, this code is well-formed:
ws.async_read(b, [](error_code, std::size_t){}); ws.async_write(b.data(), [](error_code, std::size_t){}); ws.async_ping({}, [](error_code){}); ws.async_close({}, [](error_code){});
The implementation uses composed asynchronous operations; although some individiual operations can perform both reads and writes, this behavior is coordinated internally to make sure the underlying stream is operated in a safe fashion. This allows an asynchronous read operation to respond to a received ping frame even while a user-initiated call to asynchronous write is active.