mirror of
https://github.com/boostorg/beast.git
synced 2025-08-03 14:54:32 +02:00
Remove extraneous doc file
This commit is contained in:
@@ -1,725 +0,0 @@
|
||||
[/
|
||||
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:core Core Concepts]
|
||||
|
||||
[block '''
|
||||
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
||||
<member><link linkend="beast.core.asio">Working With Asio</link></member>
|
||||
<member><link linkend="beast.core.streams">Stream Concepts</link></member>
|
||||
<member><link linkend="beast.core.buffers">Buffer Concepts</link></member>
|
||||
<member><link linkend="beast.core.async">Asynchronous Utilities</link></member>
|
||||
<member><link linkend="beast.core.tutorial">Asynchronous Composed Operation Tutorial</link></member>
|
||||
</simplelist></entry></row></tbody></tgroup></informaltable>
|
||||
''']
|
||||
|
||||
A goal of the library is expose implementation primitives in order that
|
||||
users may build their own library-like components. These primitives include
|
||||
traits, buffers, buffer algorithms, and helpers for implementing asynchronous
|
||||
operations compatible with __Asio__ and described in __N3747__. This section
|
||||
lists these facilities by group, with descriptions.
|
||||
|
||||
[important
|
||||
This documentation assumes familiarity with __Asio__. Sample
|
||||
code and identifiers used throughout are written as if the
|
||||
following declarations are in effect:
|
||||
```
|
||||
#include <beast/core.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace beast;
|
||||
|
||||
boost::asio::io_service ios;
|
||||
boost::asio::io_service work{ios};
|
||||
std::thread t{[&](){ ios.run(); }};
|
||||
|
||||
error_code ec;
|
||||
|
||||
```
|
||||
]
|
||||
|
||||
|
||||
|
||||
[heading:asio Working With Asio]
|
||||
|
||||
Beast does not manage sockets, make outgoing connections, accept incoming
|
||||
connections, or handle any aspect of connection management. In order to
|
||||
invoke library algorithms it is necessary to first have a connected socket,
|
||||
SSL stream, or other object which meets the required stream concepts. This
|
||||
example is provided as a reminder of how to work with sockets:
|
||||
```
|
||||
auto host = "www.example.com";
|
||||
boost::asio::ip::tcp::resolver r{ios};
|
||||
boost::asio::ip::tcp::socket sock{ios};
|
||||
boost::asio::connect(sock, r.resolve(
|
||||
boost::asio::ip::tcp::resolver::query{host, "http"}));
|
||||
|
||||
// At this point `sock` 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 Global Variables
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/io_service.html [*`ios`]]
|
||||
][
|
||||
A variable of type
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`]
|
||||
which is running on one separate thread, and upon which a
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/io_service__work.html `boost::asio::io_service::work`]
|
||||
object has been constructed.
|
||||
]]
|
||||
[[
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html [*`sock`]]
|
||||
][
|
||||
A variable of type
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]
|
||||
which has already been connected to a remote host.
|
||||
]]
|
||||
[[
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html [*`ssl_sock`]]
|
||||
][
|
||||
A variable of type
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream<boost::asio::ip::tcp::socket>`]
|
||||
which is already connected and has handshaked with a remote host.
|
||||
]]
|
||||
]
|
||||
|
||||
[heading:streams Stream Concepts]
|
||||
|
||||
A __Stream__ is communication channel where data expressed as octet
|
||||
buffers is transferred sequentially. Streams are either synchronous
|
||||
or asynchronous, and may allow reading, writing, or both. Note that
|
||||
a particular type may model more than one concept. For example, the
|
||||
Asio types
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]
|
||||
and
|
||||
[@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`]
|
||||
support everything. All stream algorithms in Beast are declared as
|
||||
template functions with specific concept requirements chosen from
|
||||
this list:
|
||||
|
||||
[table Stream Concepts
|
||||
[[Concept][Description]]
|
||||
[
|
||||
[__SyncReadStream__]
|
||||
[
|
||||
Supports buffer-oriented blocking reads.
|
||||
]
|
||||
][
|
||||
[__SyncWriteStream__]
|
||||
[
|
||||
Supports buffer-oriented blocking writes.
|
||||
]
|
||||
][
|
||||
[__SyncStream__]
|
||||
[
|
||||
A stream supporting buffer-oriented blocking reads and writes.
|
||||
]
|
||||
][
|
||||
[__AsyncReadStream__]
|
||||
[
|
||||
Supports buffer-oriented asynchronous reads.
|
||||
]
|
||||
][
|
||||
[__AsyncWriteStream__]
|
||||
[
|
||||
Supports buffer-oriented asynchronous writes.
|
||||
]
|
||||
][
|
||||
[__AsyncStream__]
|
||||
[
|
||||
A stream supporting buffer-oriented asynchronous reads and writes.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
These template metafunctions check whether a given type meets the
|
||||
requirements for the various stream concepts, and some additional
|
||||
useful utilities. The library uses these type checks internally
|
||||
and also provides them as public interfaces so users may use the
|
||||
same techniques to augment their own code. The use of these type
|
||||
checks helps provide more concise errors during compilation:
|
||||
|
||||
[table Stream Type Checks
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[link beast.ref.get_lowest_layer `get_lowest_layer`]
|
||||
][
|
||||
Returns `T::lowest_layer_type` if it exists, else returns `T`.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.has_get_io_service `has_get_io_service`]
|
||||
][
|
||||
Determine if the `get_io_service` member function is present,
|
||||
and returns an __io_service__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_async_read_stream `is_async_read_stream`]
|
||||
][
|
||||
Determine if a type meets the requirements of __AsyncReadStream__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_async_stream `is_async_stream`]
|
||||
][
|
||||
Determine if a type meets the requirements of both __AsyncReadStream__
|
||||
and __AsyncWriteStream__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_async_write_stream `is_async_write_stream`]
|
||||
][
|
||||
Determine if a type meets the requirements of __AsyncWriteStream__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_completion_handler `is_completion_handler`]
|
||||
][
|
||||
Determine if a type meets the requirements of __CompletionHandler__,
|
||||
and is callable with a specified signature.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_sync_read_stream `is_sync_read_stream`]
|
||||
][
|
||||
Determine if a type meets the requirements of __SyncReadStream__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_sync_stream `is_sync_stream`]
|
||||
][
|
||||
Determine if a type meets the requirements of both __SyncReadStream__
|
||||
and __SyncWriteStream__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_sync_write_stream `is_sync_write_stream`]
|
||||
][
|
||||
Determine if a type meets the requirements of __SyncWriteStream__.
|
||||
]]
|
||||
]
|
||||
|
||||
Using the type checks with `static_assert` on function or class template
|
||||
types will provide users with helpful error messages and prevent undefined
|
||||
behaviors. This example shows how a template function which writes to a
|
||||
synchronous stream may check its argument:
|
||||
```
|
||||
template<class SyncWriteStream>
|
||||
void write_string(SyncWriteStream& stream, string_view s)
|
||||
{
|
||||
static_assert(is_sync_write_stream<SyncWriteStream>::value,
|
||||
"SyncWriteStream requirements not met");
|
||||
boost::asio::write(stream, boost::asio::const_buffers_1(s.data(), s.size()));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
[heading:buffers Buffer Concepts]
|
||||
|
||||
__Asio__ provides the __ConstBufferSequence__ and __MutableBufferSequence__
|
||||
concepts, whose models provide ranges of buffers, as well as the __streambuf__
|
||||
class which encapsulates memory storage that may be automatically resized as
|
||||
required, where the memory is divided into an input sequence followed by an
|
||||
output sequence. The Networking TS (__N4588__) generalizes the `streambuf`
|
||||
interface into the __DynamicBuffer__ concept. Beast algorithms which require
|
||||
resizable buffers accept as parameters dynamic buffer objects. These
|
||||
metafunctions check types against these buffer concepts:
|
||||
|
||||
[table Buffer Type Checks
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[link beast.ref.is_const_buffer_sequence `is_const_buffer_sequence`]
|
||||
][
|
||||
Determine if a type meets the requirements of __ConstBufferSequence__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_mutable_buffer_sequence `is_mutable_buffer_sequence`]
|
||||
][
|
||||
Determine if a type meets the requirements of __MutableBufferSequence__.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.is_dynamic_buffer `is_dynamic_buffer`]
|
||||
][
|
||||
Determine if a type meets the requirements of __DynamicBuffer__.
|
||||
]]
|
||||
]
|
||||
|
||||
To suit various needs, several implementation of dynamic buffer are available:
|
||||
|
||||
[table Dynamic Buffer Implementations
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[link beast.ref.buffers_adapter `buffers_adapter`]
|
||||
][
|
||||
This wrapper adapts any __MutableBufferSequence__ into a
|
||||
__DynamicBuffer__ with an upper limit on the total size of the input and
|
||||
output areas equal to the size of the underlying mutable buffer sequence.
|
||||
The implementation does not perform heap allocations.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.flat_buffer `flat_buffer`]
|
||||
[link beast.ref.basic_flat_buffer `basic_flat_buffer`]
|
||||
][
|
||||
Guarantees that input and output areas are buffer sequences with
|
||||
length one. Upon construction an optional upper limit to the total
|
||||
size of the input and output areas may be set. The basic container
|
||||
supports the standard allocator model.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.multi_buffer `multi_buffer`]
|
||||
[link beast.ref.basic_multi_buffer `basic_multi_buffer`]
|
||||
][
|
||||
Uses a sequence of one or more character arrays of varying sizes.
|
||||
Additional character array objects are appended to the sequence to
|
||||
accommodate changes in the size of the character sequence. The basic
|
||||
container supports the standard allocator model.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.static_buffer `static_buffer`]
|
||||
[link beast.ref.static_buffer `static_buffer_n`]
|
||||
][
|
||||
Provides the facilities of a dynamic buffer, subject to an upper
|
||||
limit placed on the total size of the input and output areas defined
|
||||
by a constexpr template parameter. The storage for the sequences are
|
||||
kept in the class; the implementation does not perform heap allocations.
|
||||
]]
|
||||
]
|
||||
|
||||
Network applications frequently need to manipulate buffer sequences. To
|
||||
facilitate working with buffers the library treats these sequences as
|
||||
a special type of range. Algorithms and wrappers are provided which
|
||||
transform these buffer sequences ranges efficiently using lazy evaluation.
|
||||
No memory allocations are used in the transformations; instead, they
|
||||
create lightweight iterators over the existing, unmodified memory
|
||||
buffers. The lifetime of the transformed buffers is retained by the
|
||||
caller; ownership is not transferred.
|
||||
|
||||
[table Buffer Algorithms
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[link beast.ref.buffer_cat `buffer_cat`]
|
||||
][
|
||||
This functions returns a new buffer sequence which, when iterated,
|
||||
traverses the sequence which would be formed if all of the input buffer
|
||||
sequences were concatenated. With this routine, multiple calls to a
|
||||
stream's `write_some` function may be combined into one, eliminating
|
||||
expensive system calls.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.buffer_cat_view `buffer_cat_view`]
|
||||
][
|
||||
This class represents the buffer sequence formed by concatenating
|
||||
two or more buffer sequences. This is type of object returned by
|
||||
[link beast.ref.buffer_cat `buffer_cat`].
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.buffer_prefix `buffer_prefix`]
|
||||
][
|
||||
This function returns a new buffer or buffer sequence which represents
|
||||
a prefix of the original buffers.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.buffer_prefix_view `buffer_prefix_view`]
|
||||
][
|
||||
This class represents the buffer sequence formed from a prefix of
|
||||
an existing buffer sequence. This is the type of buffer returned by
|
||||
[link beast.ref.buffer_prefix `buffer_prefix`].
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.consuming_buffers `consuming_buffers`]
|
||||
][
|
||||
This class wraps the underlying memory of an existing buffer sequence
|
||||
and presents a suffix of the original sequence. The length of the suffix
|
||||
may be progressively shortened. This lets callers work with sequential
|
||||
increments of a buffer sequence.
|
||||
]]
|
||||
]
|
||||
|
||||
These two functions facilitate buffer interoperability with standard
|
||||
output streams.
|
||||
|
||||
[table Buffer Output Streams
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[link beast.ref.buffers `buffers`]
|
||||
][
|
||||
This function wraps a __ConstBufferSequence__ so it may be
|
||||
used with `operator<<` and `std::ostream`.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.ostream `ostream`]
|
||||
][
|
||||
This function returns a `std::ostream` which wraps a dynamic buffer.
|
||||
Characters sent to the stream using `operator<<` is stored in the
|
||||
dynamic buffer.
|
||||
]]
|
||||
]
|
||||
|
||||
|
||||
|
||||
[heading:async Asynchronous Utilities]
|
||||
|
||||
Asynchronous operations are started by calling a free function or member
|
||||
function known as an ['asynchronous initiation function]. The initiation
|
||||
function accepts parameters specific to the operation as well as a "completion
|
||||
token." This token is either a completion handler, or another type allowing for
|
||||
customization of how the result of the asynchronous operation is conveyed to
|
||||
callers. __Asio__ allows the special completion tokens __use_future__ and
|
||||
objects of type __yield_context__ to allow callers to specify the use of futures
|
||||
and coroutines respectively. This system, where the return value and method of
|
||||
indicating completion may be customize at the call site of the asynchronous
|
||||
initiation function, is known as the ['Extensible Asynchronous Model] described
|
||||
in __N3747__, and built-in to __N4588__.
|
||||
|
||||
[note
|
||||
A full explanation of completion handlers, the Extensible Asynchronous
|
||||
Model and how these asynchronous interfaces are used is beyond the
|
||||
scope of this document. Interested readers should consult the
|
||||
__Asio__ documentation.
|
||||
]
|
||||
|
||||
Since the interfaces provided here are low level, authors of libraries
|
||||
may wish to create higher level interfaces using the primitives found
|
||||
in this library. Non-trivial applications will want to provide their own
|
||||
asynchronous initiation functions which perform a series of other,
|
||||
intermediate asynchronous operations before invoking the final completion
|
||||
handler. The set of intermediate actions produced by calling an initiation
|
||||
function is known as a ['composed operation]. To ensure full interoperability
|
||||
and well-defined behavior, __Asio__ imposes requirements on the implementation
|
||||
of composed operations. A number of useful classes and macros to facilitate
|
||||
the development of composed operations and the associated asynchronous
|
||||
initiation functions used to launch them are available:
|
||||
|
||||
[table Asynchronous Helpers
|
||||
[[Name][Description]]
|
||||
[[
|
||||
[link beast.ref.async_completion `async_completion`]
|
||||
][
|
||||
This class aggregates the completion handler customization point and
|
||||
the asynchronous initiation function return value customization point
|
||||
into a single object which exposes the appropriate output types for the
|
||||
given input types, and also contains boilerplate that is necessary to
|
||||
implement an initiation function using the Extensible Model.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.async_return_type `async_return_type`]
|
||||
][
|
||||
This template alias determines the return value of an asynchronous
|
||||
initiation function given the completion token and signature. It is used
|
||||
by asynchronous initiation functions to meet the requirements of the
|
||||
Extensible Asynchronous Model.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.bind_handler `bind_handler`]
|
||||
][
|
||||
This function returns a new, nullary completion handler which when
|
||||
invoked with no arguments invokes the original completion handler with a
|
||||
list of bound arguments. The invocation is made from the same implicit
|
||||
or explicit strand as that which would be used to invoke the original
|
||||
handler. This is accomplished by using the correct overload of
|
||||
`asio_handler_invoke` associated with the original completion handler.
|
||||
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.handler_alloc `handler_alloc`]
|
||||
][
|
||||
This class meets the requirements of [*Allocator], and uses any custom
|
||||
memory allocation and deallocation hooks associated with a given handler.
|
||||
It is useful for when a composed operation requires temporary dynamic
|
||||
allocations to achieve its result. Memory allocated using this allocator
|
||||
must be freed before the final completion handler is invoked.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.handler_ptr `handler_ptr`]
|
||||
][
|
||||
This is a smart pointer container used to manage the internal state of a
|
||||
composed operation. It is useful when the state is non trivial. For example
|
||||
when the state has non-copyable or expensive to copy types. The container
|
||||
takes ownership of the final completion handler, and provides boilerplate
|
||||
to invoke the final handler in a way that also deletes the internal state.
|
||||
The internal state is allocated using the final completion handler's
|
||||
associated allocator, benefiting from all handler memory management
|
||||
optimizations transparently.
|
||||
]]
|
||||
[[
|
||||
[link beast.ref.handler_type `handler_type`]
|
||||
][
|
||||
This template alias converts a completion token and signature to the
|
||||
correct completion handler type. It is used in the implementation of
|
||||
asynchronous initiation functions to meet the requirements of the
|
||||
Extensible Asynchronous Model.
|
||||
]]
|
||||
]
|
||||
|
||||
|
||||
|
||||
[section:tutorial Asynchronous Composed Operation Tutorial]
|
||||
|
||||
To illustrate the usage of the asynchronous helpers in the core section of
|
||||
this library, we will develop a simple asynchronous composed operation called
|
||||
[*echo]. This operation will read up to the first newline on a stream, and
|
||||
then write the same line including the newline back on the stream.
|
||||
|
||||
First we define the input parameters and results, then declare our initiation
|
||||
function. For our echo operation the only inputs are the stream and the
|
||||
completion token. The output is the error code which is usually included in
|
||||
all completion handler signatures.
|
||||
|
||||
```
|
||||
#include <beast/core.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
// Read a line and echo it back
|
||||
//
|
||||
template<class AsyncStream, class CompletionToken>
|
||||
beast::async_return_type<CompletionToken, void(beast::error_code)>
|
||||
async_echo(AsyncStream& stream, CompletionToken&& token)
|
||||
```
|
||||
|
||||
Now that we have a declaration, we will define the body of the function. We
|
||||
want to achieve the following goals: perform static type checking on the input
|
||||
parameters, set up the return value as per __N3747__, and launch the composed
|
||||
operation by constructing the object and invoking it.
|
||||
|
||||
```
|
||||
template<class AsyncStream, class Handler>
|
||||
class echo_op; // This is our composed operation implementation
|
||||
|
||||
// Read a line and echo it back
|
||||
//
|
||||
template<class AsyncStream, class CompletionToken>
|
||||
beast::async_return_type<CompletionToken, void(beast::error_code)>
|
||||
async_echo(AsyncStream& stream, CompletionToken&& token)
|
||||
{
|
||||
// Make sure stream meets the requirements. We use static_assert
|
||||
// to cause a friendly message instead of an error novel.
|
||||
//
|
||||
static_assert(beast::is_async_stream<AsyncStream>::value,
|
||||
"AsyncStream requirements not met");
|
||||
|
||||
// This helper manages some of the handler's lifetime and
|
||||
// uses the result and handler specializations associated with
|
||||
// the completion token to help customize the return value.
|
||||
//
|
||||
beast::async_completion<CompletionToken, void(beast::error_code)> init{token};
|
||||
|
||||
// Create the composed operation and launch it. This is a constructor
|
||||
// call followed by invocation of operator(). We use handler_type
|
||||
// to convert the completion token into the correct handler type,
|
||||
// allowing user defined specializations of the async result template
|
||||
// to take effect.
|
||||
//
|
||||
echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{
|
||||
stream, init.completion_handler}(beast::error_code{}, 0);
|
||||
|
||||
// This hook lets the caller see a return value when appropriate.
|
||||
// For example this might return std::future<error_code> if
|
||||
// CompletionToken is boost::asio::use_future, or this might
|
||||
// return an error code if CompletionToken specifies a coroutine.
|
||||
//
|
||||
return init.result.get();
|
||||
}
|
||||
```
|
||||
|
||||
The initiation function contains a few relatively simple parts. There is the
|
||||
customization of the return value type, static type checking, building the
|
||||
return value type using the helper, and creating and launching the composed
|
||||
operation object. The [*`echo_op`] object does most of the work here, and has
|
||||
a somewhat non-trivial structure. This structure is necessary to meet the
|
||||
stringent requirements of composed operations (described in more detail in
|
||||
the __Asio__ documentation). We will touch on these requirements without
|
||||
explaining them in depth.
|
||||
|
||||
First we will create boilerplate which is present in all composed operations
|
||||
written in this style:
|
||||
|
||||
```
|
||||
// This composed operation reads a line of input and echoes it back.
|
||||
//
|
||||
template<class AsyncStream, class Handler>
|
||||
class echo_op
|
||||
{
|
||||
// This holds all of the state information required by the operation.
|
||||
struct state
|
||||
{
|
||||
// The stream to read and write to
|
||||
AsyncStream& stream;
|
||||
|
||||
// Indicates what step in the operation's state machine
|
||||
// to perform next, starting from zero.
|
||||
int step = 0;
|
||||
|
||||
// The buffer used to hold the input and output data.
|
||||
// Note that we use a custom allocator for performance,
|
||||
// this allows the implementation of the io_service to
|
||||
// make efficient re-use of memory allocated by composed
|
||||
// operations during continuations.
|
||||
//
|
||||
boost::asio::basic_streambuf<beast::handler_alloc<char, Handler>> buffer;
|
||||
|
||||
// handler_ptr requires that the first parameter to the
|
||||
// contained object constructor is a reference to the
|
||||
// managed final completion handler.
|
||||
//
|
||||
explicit state(Handler& handler, AsyncStream& stream_)
|
||||
: stream(stream_)
|
||||
, buffer((std::numeric_limits<std::size_t>::max)(),
|
||||
beast::handler_alloc<char, Handler>{handler})
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// This smart pointer container allocates our state using the
|
||||
// memory allocation hooks associated with the final completion
|
||||
// handler, manages the lifetime of that handler for us, and
|
||||
// enforces the destroy-before-invocation requirement on memory
|
||||
// allocated using the hooks.
|
||||
//
|
||||
beast::handler_ptr<state, Handler> p_;
|
||||
|
||||
public:
|
||||
// Boost.Asio requires that handlers are CopyConstructible.
|
||||
// In some cases, it takes advantage of handlers that are
|
||||
// MoveConstructible. This operation supports both.
|
||||
//
|
||||
echo_op(echo_op&&) = default;
|
||||
echo_op(echo_op const&) = default;
|
||||
|
||||
// The constructor simply creates our state variables in
|
||||
// the smart pointer container.
|
||||
//
|
||||
template<class DeducedHandler, class... Args>
|
||||
echo_op(AsyncStream& stream, DeducedHandler&& handler)
|
||||
: p_(std::forward<DeducedHandler>(handler), stream)
|
||||
{
|
||||
}
|
||||
|
||||
// Determines if the next asynchronous operation represents a
|
||||
// continuation of the asynchronous flow of control associated
|
||||
// with the final handler. If we are past step one, it means
|
||||
// we have performed an asynchronous operation therefore any
|
||||
// subsequent operation would represent a continuation.
|
||||
// Otherwise, we propagate the handler's associated value of
|
||||
// is_continuation. Getting this right means the implementation
|
||||
// may schedule the invokation of the invoked functions more
|
||||
// efficiently.
|
||||
//
|
||||
friend bool asio_handler_is_continuation(echo_op* op)
|
||||
{
|
||||
// This next call is structured to permit argument
|
||||
// dependent lookup to take effect.
|
||||
using boost::asio::asio_handler_is_continuation;
|
||||
|
||||
// Always use std::addressof to pass the pointer to the handler,
|
||||
// otherwise an unwanted overload of operator& may be called instead.
|
||||
return op->p_->step > 1 ||
|
||||
asio_handler_is_continuation(std::addressof(op->p_.handler()));
|
||||
}
|
||||
|
||||
// Handler hook forwarding. These free functions invoke the hooks
|
||||
// associated with the final completion handler. In effect, they
|
||||
// make the Asio implementation treat our composed operation the
|
||||
// same way it would treat the final completion handler for the
|
||||
// purpose of memory allocation and invocation.
|
||||
//
|
||||
// Our implementation just passes through the call to the hook
|
||||
// associated with the final handler.
|
||||
|
||||
friend void* asio_handler_allocate(std::size_t size, echo_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_allocate;
|
||||
return asio_handler_allocate(size, std::addressof(op->p_.handler()));
|
||||
}
|
||||
|
||||
friend void asio_handler_deallocate(void* p, std::size_t size, echo_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_deallocate;
|
||||
return asio_handler_deallocate(p, size, std::addressof(op->p_.handler()));
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend void asio_handler_invoke(Function&& f, echo_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_invoke;
|
||||
return asio_handler_invoke(f, std::addressof(op->p_.handler()));
|
||||
}
|
||||
|
||||
// Our main entry point. This will get called as our
|
||||
// intermediate operations complete. Definition below.
|
||||
//
|
||||
void operator()(beast::error_code ec, std::size_t bytes_transferred);
|
||||
};
|
||||
```
|
||||
|
||||
We have the common boilerplate for a composed operation and now we just need
|
||||
to implement the function call operator. Our strategy is to make our composed
|
||||
object meet the requirements of a completion handler by being copyable (also
|
||||
movable), and by providing the function call operator with the correct
|
||||
signature. Rather than using `std::bind` or `boost::bind`, which destroys
|
||||
the type information and therefore breaks the allocation and invocation
|
||||
hooks, we will simply pass `std::move(*this)` as the completion handler
|
||||
parameter for any operations that we initiate. For the move to work correctly,
|
||||
care must be taken to ensure that no access to data members are made after the
|
||||
move takes place. Here is the implementation of the function call operator for
|
||||
this echo operation:
|
||||
```
|
||||
// We are callable with the signature void(error_code, bytes_transferred),
|
||||
// allowing `*this` to be used as both a ReadHandler and a WriteHandler.
|
||||
//
|
||||
template<class AsyncStream, class Handler>
|
||||
void echo_op<AsyncStream, Handler>::
|
||||
operator()(beast::error_code ec, std::size_t bytes_transferred)
|
||||
{
|
||||
// Store a reference to our state. The address of the state won't
|
||||
// change, and this solves the problem where dereferencing the
|
||||
// data member is undefined after a move.
|
||||
auto& p = *p_;
|
||||
|
||||
// Now perform the next step in the state machine
|
||||
switch(ec ? 2 : p.step)
|
||||
{
|
||||
// initial entry
|
||||
case 0:
|
||||
// read up to the first newline
|
||||
p.step = 1;
|
||||
return boost::asio::async_read_until(p.stream, p.buffer, "\n", std::move(*this));
|
||||
|
||||
case 1:
|
||||
// write everything back
|
||||
p.step = 2;
|
||||
// async_read_until could have read past the newline,
|
||||
// use buffer_prefix to make sure we only send one line
|
||||
return boost::asio::async_write(p.stream,
|
||||
beast::buffer_prefix(bytes_transferred, p.buffer.data()), std::move(*this));
|
||||
|
||||
case 2:
|
||||
p.buffer.consume(bytes_transferred);
|
||||
break;
|
||||
}
|
||||
|
||||
// Invoke the final handler. If we wanted to pass any arguments
|
||||
// which come from our state, they would have to be moved to the
|
||||
// stack first, since the `handler_ptr` guarantees that the state
|
||||
// is destroyed before the handler is invoked.
|
||||
//
|
||||
p_.invoke(ec);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
A complete, runnable version of this example may be found in the examples
|
||||
directory.
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[endsect]
|
Reference in New Issue
Block a user