From a345327c7b05f6ca76e439053e1c2dcd02184e74 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 12 Aug 2016 19:20:34 -0400 Subject: [PATCH] Add WebSocket implementation comparison doc --- doc/design.qbk | 474 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 450 insertions(+), 24 deletions(-) diff --git a/doc/design.qbk b/doc/design.qbk index ff109d76..038c3dbd 100644 --- a/doc/design.qbk +++ b/doc/design.qbk @@ -8,9 +8,9 @@ [section:design Design choices] The implementations are driven by business needs of cryptocurrency server -applications ([@https://ripple.com Ripple] written in C++. These -needs were not met by existing solutions so new code was written. The new -code tries to avoid design flaws encountered in the already-existing software +applications (e.g. [@https://ripple.com Ripple]) written in C++. These +needs were not met by existing solutions so Beast was written from scratch +as a solution. Beast's design philosophy avoid flaws exhibited by other libraries: * Don't sacrifice performance. @@ -44,11 +44,13 @@ to address those issues. in production. That would give some evidence that the design works in practice."" ][ - Beast.HTTP and Beast.WebSocket will be used in [*rippled], an - asynchronous peer to peer server that implements the - [*Ripple Consensus Protocol]. These servers are deployed in multiple - production environments, with banks in many countries running client - applications that connect to [*rippled]. + Beast.HTTP and Beast.WebSocket are production ready and currently + running on public servers receiving traffic and handling millions of + dollars worth of financial transactions daily. The servers run [*rippled], + open source software ([@https://github.com/ripple/rippled repository]) + implementing the + [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], + technology provided by [@http://ripple.com Ripple]. ]] ] @@ -171,26 +173,453 @@ start. Other design goals: [section:websocket WebSocket] [variablelist + +[[ + How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], + an alternate header-only WebSocket implementation? +][ + [variablelist + + [[1. Synchronous Interface][ + + Beast offers full support for WebSockets using a synchronous interface. It + uses the same style of interfaces found in Boost.Asio: versions that throw + exceptions, or versions that return the error code in a reference parameter: + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [websocketpp] + ][ + [``` + template + void + read(opcode& op, Streambuf& streambuf) + ```] + [ + // + ] + ]]]] + + [[2. Connection Model][ + + websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` + ([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) + To get an idea of the complexity involved with implementing a transport, + compare the asio transport to the + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] + (a layer that allows websocket communication over a std iostream). + + In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] + template argument The type requirements for [*`NextLayer`] are + already familiar to users as they are documented in Asio: + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html SyncReadStream], + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncWriteStream.html SyncWriteStream], + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html AsyncReadStream], and + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncWriteStream.html AsyncWriteStream]. + The type requirements for instantiating `beast::websocket::stream` versus + `websocketpp::connection` with user defined types are vastly reduced + (18 functions versus 2). Note that websocketpp connections are passed by + `shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. + A `beast::websocket::stream` is constructible and movable in a manner identical + `to a boost::asio::ip::tcp::socket`. Callers can put such objects in a + `shared_ptr` if they want to, but there is no requirement to do so. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] + ][ + [``` + template + class stream + { + NextLayer next_layer_; + ... + } + ```] + [``` + template + class connection + : public config::transport_type::transport_con_type + , public config::connection_base + { + public: + typedef lib::shared_ptr ptr; + ... + } + ```] + ]]]] + + [[3. Client and Server Role][ + + websocketpp provides multi-role support through a hierarchy of + different classes. A `beast::websocket::stream` is role-agnostic, it + offers member functions to perform both client and server handshakes + in the same class. The same types are used for client and server + streams. + + [table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] + ][ + [ + // + ] + [``` + template + class client : public endpoint,config>; + template + class server : public endpoint,config>; + ```] + ]]]] + + [[4. Thread Safety][ + + websocketpp uses mutexes to protect shared data from concurrent + access. In contrast, Beast does not use mutexes anywhere in its + implementation. Instead, it follows the Asio pattern. Calls to + asynchronous initiation functions use the same method to invoke + intermediate handlers as the method used to invoke the final handler, + through the + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/asio_handler_invoke.html asio_handler_invoke] mechanism. + + The only requirement in Beast is that calls to asynchronous initiation + functions are made from the same implicit or explicit strand. For + example, if the `io_service` associated with a `beast::websocket::stream` + is single threaded, this counts as an implicit strand and no performance + costs associated with mutexes are incurred. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] + ][ + [``` + template + friend + void asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); + } + ```] + [``` + mutex_type m_read_mutex; + ```] + ]]]] + + [[5. Callback Model][ + + websocketpp requires a one-time call to set the handler for each event + in its interface (for example, upon message receipt). The handler is + represented by a `std::function equivalent`. Its important to recognize + that the websocketpp interface performs type-erasure on this handler. + + In comparison, Beast handlers are specified in a manner identical to + Boost.Asio. They are function objects which can be copied or moved but + most importantly they are not type erased. The compiler can see + through the type directly to the implementation, permitting + optimization. Furthermore, Beast follows the Asio rules for treatment + of handlers. It respects any allocation, continuation, or invocation + customizations associated with the handler through the use of argument + dependent lookup overloads of functions such as `asio_handler_allocate`. + + The Beast completion handler is provided at the call site. For each + call to an asynchronous initiation function, it is guaranteed that + there will be exactly one final call to the handler. This functions + exactly the same way as the asynchronous initiation functions found in + Boost.Asio, allowing the composition of higher level abstractions. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] + ][ + [``` + template + typename async_completion::result_type + async_read(opcode& op, Streambuf& streambuf, ReadHandler&& handler); + ```] + [``` + typedef lib::function message_handler; + void set_message_handler(message_handler h); + ```] + ]]]] + + [[6. Extensible Asynchronous Model][ + + Beast fully supports the + [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] + developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). + + Beast websocket asynchronous interfaces may be used seamlessly with + `std::future` stackful/stackless coroutines, or user defined customizations. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] + [websocketpp] + ][ + [``` + beast::async_completion completion(handler); + read_op{ + completion.handler, *this, op, streambuf}; + return completion.result.get(); + ```] + [ + // + ] + ]]]] + + [[7. Message Buffering][ + + websocketpp defines a message buffer, passed in arguments by + `shared_ptr`, and an associated message manager which permits + aggregation and memory reuse of memory. The implementation of + `websocketpp::message` uses a `std::string` to hold the payload. If an + incoming message is broken up into multiple frames, the string may be + reallocated for each continuation frame. The std::string always uses + the standard allocator, it is not possible to customize the choice of + allocator. + + Beast allows callers to specify the object for receiving the message + or frame data, which is of any type meeting the requirements of + [@http://vinniefalco.github.io/beast/beast/types/DynamicBuffer.html [*DynamicBuffer]] + (modeled after `boost::asio::streambuf`). + + Beast comes with the class `beast::basic_streambuf`, an efficient + implementation of the [*DynamicBuffer] concept which makes use of multiple + allocated octet arrays. If an incoming message is broken up into + multiple pieces, no reallocation occurs. Instead, new allocations are + appended to the sequence when existing allocations are filled. Beast + does not impose any particular memory management model on callers. The + `basic_streambuf` provided by beast supports standard allocators through + a template argument. Use the [*DynamicBuffer] that comes with beast, + customize the allocator if you desire, or provide your own type that + meets the + [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/basic_streambuf.hpp#L21 concept requirements]. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] + ][ + [``` + template + read(opcode& op, DynamicBuffer& dynabuf); + ```] + [``` + template class con_msg_manager> + class message { + public: + typedef lib::shared_ptr ptr; + ... + std::string m_payload; + ... + }; + ```] + ]]]] + + [[8. Sending Messages][ + + When sending a message, websocketpp requires that the payload is + packaged in a `websocketpp::message` object using `std::string` as the + storage, or it requires a copy of the caller provided buffer by + constructing a new message object. Messages are placed onto an + outgoing queue. An asynchronous write operation runs in the background + to clear the queue. No user facing handler can be registered to be + notified when messages or frames have completed sending. + + Beast doesn't allocate or make copies of buffers when sending data. The + caller's buffers are sent in-place. You can use any object meeting the + requirements of + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], + permitting efficient scatter-gather I/O. + + The [*ConstBufferSequence] interface allows callers to send data from + memory-mapped regions (not possible in websocketpp). Callers can also + use the same buffers to send data to multiple streams, for example + broadcasting common subscription data to many clients at once. For + each call to `async_write` the completion handler is called once when + the data finishes sending, in a manner identical to `boost::asio::async_write`. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] + ][ + [``` + template + void + write(ConstBufferSequence const& buffers); + ```] + [``` + lib::error_code send(std::string const & payload, + frame::opcode::value op = frame::opcode::text); + ... + lib::error_code send(message_ptr msg); + ```] + ]]]] + + [[9. Streaming Messages][ + + websocketpp requires that the entire message fit into memory, and that + the size is known ahead of time. + + Beast allows callers to compose messages in individual frames. This is + useful when the size of the data is not known ahead of time or if it + is not desired to buffer the entire message in memory at once before + sending it. For example, sending periodic output of a database query + running on a coroutine. Or sending the contents of a file in pieces, + without bringing it all into memory. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] + [websocketpp] + ][ + [``` + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers); + ```] + [ + // + ] + ]]]] + + [[10. Flow Control][ + + The websocketpp read implementation continuously reads asynchronously + from the network and buffers message data. To prevent unbounded growth + and leverage TCP/IP's flow control mechanism, callers can periodically + turn this 'read pump' off and back on. + + In contrast a `beast::websocket::stream` does not independently begin + background activity, nor does it buffer messages. It receives data only + when there is a call to an asynchronous initiation function (for + example `beast::websocket::stream::async_read`) with an associated handler. + Applications do not need to implement explicit logic to regulate the + flow of data. Instead, they follow the traditional model of issuing a + read, receiving a read completion, processing the message, then + issuing a new read and repeating the process. + + [table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] + ][ + [ + // + ] + [``` + lib::error_code pause_reading(); + lib::error_code resume_reading(); + ```] + ]]]] + + [[11. Connection Establishment][ + + websocketpp offers the `endpoint` class which can handle binding and + listening to a port, and spawning connection objects. + + Beast does not reinvent the wheel here, callers use the interfaces + already in `boost::asio` for receiving incoming connections resolving + host names, or establishing outgoing connections. After the socket (or + `boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` + is constructed around it and the WebSocket handshake can be performed. + + Beast users are free to implement their own "connection manager", but + there is no requirement to do so. + + [table + [ + [[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/async_connect.html Beast], + [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] + ][ + [``` + #include + ```] + [``` + template + class endpoint : public config::socket_type; + ```] + ]]]] + + [[12. WebSocket Handshaking][ + + Callers invoke `beast::websocket::accept` to perform the WebSocket + handshake, but there is no requirement to use this function. Advanced + users can perform the WebSocket handshake themselves. Beast WebSocket + provides the tools for composing the request or response, and the + Beast HTTP interface provides the container and algorithms for sending + and receiving HTTP/1 messages including the necessary HTTP Upgrade + request for establishing the WebSocket session. + + Beast allows the caller to pass the incoming HTTP Upgrade request for + the cases where the caller has already received an HTTP message. + This flexibility permits novel and robust implementations. For example, + a listening socket that can handshake in multiple protocols on the + same port. + + Sometimes callers want to read some bytes on the socket before reading + the WebSocket HTTP Upgrade request. Beast allows these already-received + bytes to be supplied to an overload of the accepting function to permit + sophisticated features. For example, a listening socket that can + accept both regular WebSocket and Secure WebSocket (SSL) connections. + + [table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], + [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] + [websocketpp] + ][ + [``` + template + void + accept(ConstBufferSequence const& buffers); + + template + void + accept(http::request_v1 const& request); + ```] + [ + // + ] + ]]]] + + ] +]] + [[ What about message compression? ][ - The feature is not currently present in the library, but the choice - of type requirements for buffers passed to the read functions have been - made with compression in mind. There is the plan to add this feature; - however, we feel that even without compression users can begin taking - advantage of the WebSocket protocol immediately with this library. + The author is currently porting ZLib 1.2.8 to modern, header-only C++11 + that does not use macros or try to support ancient architectures. This + deflate implementation will be available as its own individually + usable interface, and also will be used to power Beast WebSocket's + permessage-deflate implementation, due Q4 of 2016. + + However, Beast currently has sufficient functionality that users can + begin taking advantage of the WebSocket protocol using this library + immediately. ]] [[ Where is the TLS/SSL interface? ][ - The `websocket::stream` just wraps the socket or stream that you - provide (for example, a `boost::asio::ip::tcp::socket` or a - `boost::asio::ssl::stream`). You establish your TLS connection - using the interface on `ssl::stream` like shown in all of the Asio - examples, they construct your `websocket::stream` around it. - It works perfectly fine - Beast.WebSocket doesn't try to reinvent the - wheel or put a fresh coat of interface paint on the `ssl::stream`. + The `websocket::stream` wraps the socket or stream that you provide + (for example, a `boost::asio::ip::tcp::socket` or a + `boost::asio::ssl::stream`). You establish your TLS connection using the + interface on `ssl::stream` like shown in all of the Asio examples, they + construct your `websocket::stream` around it. It works perfectly fine; + Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of + interface paint on the `ssl::stream`. The WebSocket implementation [*does] provides support for shutting down the TLS connection through the use of the ADL compile-time virtual functions @@ -200,14 +629,11 @@ start. Other design goals: for TLS streams. Callers may provide their own overloads of these functions for user-defined next layer types. ]] + ] [endsect] - - - - [endsect]