|  | Home | Libraries | People | FAQ | More | 
To use Beast effectively, a prior understanding of Networking is required. This section reviews these concepts as a reminder and guide for further learning.
A network allows programs located anywhere to exchange information after opting-in to communications by establishing a connection. Data may be reliably transferred across a connection in both directions (full-duplex) with bytes arriving in the same order they were sent. These connections, along with the objects and types used to represent them, are collectively termed streams. The computer or device attached to the network is called a host, and the program on the other end of an established connection is called a peer.
The internet is a global network of interconnected computers that use a variety of standardized communication protocols to exchange information. The most popular protocol is TCP/IP, which this library relies on exclusively. The protocol takes care of the low level details so that applications see a stream, which is the reliable, full-duplex connection carrying the ordered set of bytes described above. A server is a powerful, always-on host at a well-known network name or network address which provides data services. A client is a transient peer which connects to a server to exchange data, and goes offline.
A vendor supplies a program called a device driver, enabling networking hardware such as an ethernet adaptor to talk to the operating system. This in turn permits running programs to interact with networking using various flavors of interfaces such as Berkeley sockets or Windows Sockets 2 ("Winsock").
Networking in C++, represented by Boost.Asio, Asio, and Networking TS, provides a layer of abstraction to interact portably with the operating system facilities for not just networking but general input/output ("I/O").
        A buffer
        holds a contiguous sequence of bytes used when performing I/O. The types
        net::const_buffer
        and net::mutable_buffer
        represent these memory regions as type-safe pointer/size pairs:
      
net::const_buffer cb("Hello, world!", 13); assert(string_view(reinterpret_cast<char const*>( cb.data()), cb.size()) == "Hello, world!"); char storage[13]; net::mutable_buffer mb(storage, sizeof(storage)); std::memcpy(mb.data(), cb.data(), mb.size()); assert(string_view(reinterpret_cast<char const*>( mb.data()), mb.size()) == "Hello, world!");
| ![[Tip]](../../../../../../doc/src/images/tip.png) | Tip | 
|---|---|
| 
           | 
        The concepts ConstBufferSequence
        and MutableBufferSequence
        describe bidirectional ranges whose value type is convertible to const_buffer and mutable_buffer
        respectively. These sequences allow transacting with multiple buffers in
        a single function call, a technique called scatter/gather
        I/O. Buffers and buffer sequences are non-owning; copies
        produce shallow references and not duplicates of the underlying memory. Each
        of these statements declares a buffer sequence:
      
net::const_buffer b1; // a ConstBufferSequence by definition net::mutable_buffer b2; // a MutableBufferSequence by definition std::array<net::const_buffer, 3> b3; // A ConstBufferSequence by named requirements
        The functions net::buffer_size
        and net::buffer_copy
        determine the total number of bytes in a buffer sequence, and transfer some
        or all of bytes from one buffer sequence to another respectively. The function
        buffer_size is a customization
        point: user defined overloads in foreign namespaces are possible, and callers
        should invoke buffer_size
        without namespace qualification. The functions net::buffer_sequence_begin
        and net::buffer_sequence_end
        are used to obtain a pair of iterators for traversing the sequence. Beast
        provides a set of buffer sequence types and algorithms such as buffers_cat, buffers_front, buffers_prefix, buffers_range, and buffers_suffix. This example returns
        the bytes in a buffer sequence as a string:
      
template <class ConstBufferSequence> std::string string_from_buffers (ConstBufferSequence const& buffers) { // check that the type meets the requirements using the provided type traits static_assert( net::is_const_buffer_sequence<ConstBufferSequence>::value, "ConstBufferSequence type requirements not met"); // optimization: reserve all the space for the string first std::string result; result.reserve(beast::buffer_bytes(buffers)); // beast version of net::buffer_size // iterate over each buffer in the sequence and append it to the string for(auto it = net::buffer_sequence_begin(buffers); // returns an iterator to beginning of the sequence it != net::buffer_sequence_end(buffers);) // returns a past-the-end iterator to the sequence { // A buffer sequence iterator's value_type is always convertible to net::const_buffer net::const_buffer buffer = *it++; // A cast is always required to out-out of type-safety result.append(static_cast<char const*>(buffer.data()), buffer.size()); } return result; }
        The DynamicBuffer
        concept defines a resizable buffer sequence interface. Algorithms may be
        expressed in terms of dynamic buffers when the memory requirements are not
        known ahead of time, for example when reading an HTTP message from a stream.
        Beast provides a well-rounded collection of dynamic buffer types such as
        buffers_adaptor,
        flat_buffer,
        multi_buffer,
        and static_buffer.
        The following function reads data from a tcp_stream into a dynamic buffer
        until it encountering a newline character, using net::buffers_iterator
        to treat the contents of the buffer as a range of characters:
      
// Read a line ending in '\n' from a socket, returning // the number of characters up to but not including the newline template <class DynamicBuffer> std::size_t read_line(net::ip::tcp::socket& sock, DynamicBuffer& buffer) { // this alias keeps things readable using range = net::buffers_iterator< typename DynamicBuffer::const_buffers_type>; for(;;) { // get iterators representing the range of characters in the buffer auto begin = range::begin(buffer.data()); auto end = range::end(buffer.data()); // search for "\n" and return if found auto pos = std::find(begin, end, '\n'); if(pos != range::end(buffer.data())) return std::distance(begin, end); // Determine the number of bytes to read, // using available capacity in the buffer first. std::size_t bytes_to_read = std::min<std::size_t>( std::max<std::size_t>(512, // under 512 is too little, buffer.capacity() - buffer.size()), std::min<std::size_t>(65536, // and over 65536 is too much. buffer.max_size() - buffer.size())); // Read up to bytes_to_read bytes into the dynamic buffer buffer.commit(sock.read_some(buffer.prepare(bytes_to_read))); } }
        Synchronous input and output is accomplished through blocking function calls
        that return with the result of the operation. Such operations typically cannot
        be canceled and do not have a method for setting a timeout. The SyncReadStream
        and SyncWriteStream
        concepts define requirements for synchronous streams:
        a portable I/O abstraction that transfers data using buffer sequences to
        represent bytes and either error_code
        or an exception to report any failures. net::basic_stream_socket
        is a synchronous stream commonly used to form TCP/IP connections. User-defined
        types which meet the requirements are possible:
      
// Meets the requirements of SyncReadStream struct sync_read_stream { // Returns the number of bytes read upon success, otherwise throws an exception template <class MutableBufferSequence> std::size_t read_some(MutableBufferSequence const& buffers); // Returns the number of bytes read successfully, sets the error code if a failure occurs template <class MutableBufferSequence> std::size_t read_some(MutableBufferSequence const& buffers, error_code& ec); }; // Meets the requirements of SyncWriteStream struct sync_write_stream { // Returns the number of bytes written upon success, otherwise throws an exception template <class ConstBufferSequence> std::size_t write_some(ConstBufferSequence const& buffers); // Returns the number of bytes written successfully, sets the error code if a failure occurs template <class ConstBufferSequence> std::size_t write_some(ConstBufferSequence const& buffers, error_code& ec); };
A synchronous stream algorithm is written as a function template accepting a stream object meeting the named requirements for synchronous reading, writing, or both. This example shows an algorithm which writes text and uses exceptions to indicate errors:
template <class SyncWriteStream> void hello (SyncWriteStream& stream) { net::const_buffer cb("Hello, world!", 13); do { auto bytes_transferred = stream.write_some(cb); // may throw cb += bytes_transferred; // adjust the pointer and size } while (cb.size() > 0); }
The same algorithm may be expressed using error codes instead of exceptions:
template <class SyncWriteStream> void hello (SyncWriteStream& stream, error_code& ec) { net::const_buffer cb("Hello, world!", 13); do { auto bytes_transferred = stream.write_some(cb, ec); cb += bytes_transferred; // adjust the pointer and size } while (cb.size() > 0 && ! ec); }
An asynchronous operation begins with a call to an initiating function, which starts the operation and returns to the caller immediately. This outstanding asynchronous operation proceeds concurrently without blocking the caller. When the externally observable side effects are fully established, a movable function object known as a completion handler provided in the initiating function call is queued for execution with the results, which may include the error code and other specific information. An asynchronous operation is said to be completed after the completion handler is queued. The code that follows shows how some text may be written to a socket asynchronously, invoking a lambda when the operation is complete:
// initiate an asynchronous write operation net::async_write(sock, net::const_buffer("Hello, world!", 13), [](error_code ec, std::size_t bytes_transferred) { // this lambda is invoked when the write operation completes if(! ec) assert(bytes_transferred == 13); else std::cerr << "Error: " << ec.message() << "\n"; }); // meanwhile, the operation is outstanding and execution continues from here
        Every completion handler (also referred to as a continuation)
        has both an associated
        allocator returned by net::get_associated_allocator,
        and an associated
        executor returned by net::get_associated_executor.
        These associations may be specified intrusively:
      
// The following is a completion handler expressed // as a function object, with a nested associated // allocator and a nested associated executor. struct handler { using allocator_type = std::allocator<char>; allocator_type get_allocator() const noexcept; using executor_type = boost::asio::io_context::executor_type; executor_type get_executor() const noexcept; void operator()(boost::beast::error_code, std::size_t); };
        Or these associations may be specified non-intrusively, by specializing the
        class templates net::associated_allocator
        and net::associated_executor:
      
namespace boost { namespace asio { template<class Allocator> struct associated_allocator<handler, Allocator> { using type = std::allocator<void>; static type get(handler const& h, Allocator const& alloc = Allocator{}) noexcept; }; template<class Executor> struct associated_executor<handler, Executor> { using type = any_io_executor; static type get(handler const& h, Executor const& ex = Executor{}) noexcept; }; } // boost } // asio
        The function net::bind_executor
        may be used when the caller wants to change the executor of a completion
        handler.
      
        The allocator is used by the implementation to obtain any temporary storage
        necessary to perform the operation. Temporary allocations are always freed
        before the completion handler is invoked. The executor is a cheaply copyable
        object providing the algorithm used to invoke the completion handler. Unless
        customized by the caller, a completion handler defaults to using std::allocator<void>
        and the executor of the corresponding I/O object.
      
        Networking prescribes facilities to determine the context in which handlers
        run. Every I/O object refers to an ExecutionContext
        for obtaining the Executor
        instance used to invoke completion handlers. An executor determines where
        and how completion handlers are invoked. Executors obtained from an instance
        of net::io_context
        offer a basic guarantee: handlers will only be invoked from threads which
        are currently calling net::io_context::run.
      
        The AsyncReadStream
        and AsyncWriteStream
        concepts define requirements for asynchronous streams:
        a portable I/O abstraction that exchanges data asynchronously using buffer
        sequences to represent bytes and error_code
        to report any failures. An asynchronous stream algorithm
        is written as a templated initiating function template accepting a stream
        object meeting the named requirements for asynchronous reading, writing,
        or both. This example shows an algorithm which writes some text to an asynchronous
        stream:
      
template <class AsyncWriteStream, class WriteHandler> void async_hello (AsyncWriteStream& stream, WriteHandler&& handler) { net::async_write (stream, net::buffer("Hello, world!", 13), std::forward<WriteHandler>(handler)); }
        I/O objects such as sockets and streams are not thread-safe.
        Although it is possible to have more than one operation outstanding (for
        example, a simultaneous asynchronous read and asynchronous write) the stream
        object itself may only be accessed from one thread at a time. This means
        that member functions such as move constructors, destructors, or initiating
        functions must not be called concurrently. Usually this is accomplished with
        synchronization primitives such as a mutex, but concurrent network programs
        need a better way to access shared resources, since acquiring ownership of
        a mutex could block threads from performing uncontended work. For efficiency,
        networking adopts a model of using threads without explicit locking by requiring
        all access to I/O objects to be performed within a strand.
      
        Because completion handlers cause an inversion of the flow of control, sometimes
        other methods of attaching a continuation are desired. Networking provides
        the Universal
        Model for Asynchronous Operations, providing a customizable
        means for transforming the signature of the initiating function to use other
        types of objects and methods in place of a completion handler callback. For
        example to call to write a string to a socket asynchronously, using a std::future
        to receive the number of bytes transferred thusly looks like this:
      
std::future<std::size_t> f = net::async_write(sock, net::const_buffer("Hello, world!", 13), net::use_future);
        This functionality is enabled by passing the variable net::use_future
        (of type net::use_future_t<>) in place of the completion
        handler. The same async_write
        function overload can work with a fiber
        launched with asio::spawn:
      
asio::spawn( [&sock](net::yield_context yield) { std::size_t bytes_transferred = net::async_write(sock, net::const_buffer("Hello, world!", 13), yield); (void)bytes_transferred; });
        In both of these cases, an object with a specific type is used in place of
        the completion handler, and the return value of the initiating function is
        transformed from void to std::future<std::size_t> or std::size_t.
        The handler is sometimes called a CompletionToken
        when used in this context. The return type transformation is supported by
        customization points in the initiating function signature. Here is the signature
        for net::async_write:
      
template< class AsyncWriteStream, class ConstBufferSequence, class CompletionToken> auto async_write( AsyncWriteStream* stream, // references are passed as pointers ConstBufferSequence const& buffers, CompletionToken&& token) // a handler, or a special object. -> typename net::async_result< // return-type customization point. typename std::decay<CompletionToken>::type, // type used to specialize async_result. void(error_code, std::size_t) // underlying completion handler signature. >::return_type;
        The type of the function's return value is determined by the net::async_result
        customization point, which comes with specializations for common library
        types such as std::future and may also be specialized for
        user-defined types. The body of the initiating function calls the net::async_initiate
        helper to capture the arguments and forward them to the specialization of
        async_result. An additional
        "initiation function" object is provided which async_result
        may use to immediately launch the operation, or defer the launch of the operation
        until some point in the future (this is called "lazy execution").
        The initiation function object receives the internal completion handler which
        matches the signature expected by the initiating function:
      
return net::async_initiate< CompletionToken, void(error_code, std::size_t)>( run_async_write{}, // The "initiation" object. token, // Token must come before other arguments. &stream, // Additional captured arguments are buffers); // forwarded to the initiation object.
        This transformed, internal handler is responsible for the finalizing step
        that delivers the result of the operation to the caller. For example, when
        using net::use_future the internal handler will deliver
        the result by calling std::promise::set_value
        on the promise object returned by the initiating function.
      
        Most library stream algorithms require a tcp::socket,
        net::ssl::stream,
        or other Stream
        object that has already established communication with a remote peer. This
        example is provided as a reminder of how to work with sockets:
      
// The resolver is used to look up IP addresses and port numbers from a domain and service name pair tcp::resolver r{ioc}; // A socket represents the local end of a connection between two peers tcp::socket stream{ioc}; // Establish a connection before sending and receiving data net::connect(stream, r.resolve("www.example.com", "http")); // At this point `stream` is a connected to a remote // host and may be used to perform stream operations.
Throughout this documentation identifiers with the following names have special meaning:
Table 1.3. Global Variables
| Name | Description | 
|---|---|
| 
                  A variable of type  | |
| 
                  A variable of type  | |
| 
                  A variable of type  | |
| 
                  A variable of type  |