beast/doc/qbk/03_core/7a_echo.qbk
2019-07-07 12:29:56 -07:00

81 lines
3.7 KiB
Plaintext

[/
Copyright (c) 2016-2019 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)
Official repository: https://github.com/boostorg/beast
]
[section:echo Echo __example__]
This example develops an initiating function called [*echo].
The 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.
[example_core_echo_op_2]
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 an intermediate, stateful completion
handler and invoking it.
The initiating 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
`echo_op` composed operation object.
The implementation strategy is to make the composed object meet the
requirements of a completion handler by being movable, and by making it
invocable so it can be used as a continuation for the asynchronous operations
it launches. 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 complete implementation of our composed operation:
[example_core_echo_op_3]
There are some common mistakes that should be avoided when writing
composed operations:
* Type erasing the final handler. This will cause undefined behavior.
* Forgetting to include a return statement after calling an
initiating function.
* Calling a synchronous function by accident. In general composed
operations should not block for long periods of time, since this
ties up a thread running on the __io_context__.
* Forgetting to provide `executor_type` and `get_executor` for the
composed operation. This will cause undefined behavior. For example,
if someone calls the initiating function with a strand-wrapped
function object, and there is more than thread running on the
__io_context__, the underlying stream may be accessed in a fashion
that violates safety guarantees. Beast provides class templates
to take care of this boilerplate for you.
* Forgetting to create an object of type __executor_work_guard__ with the
type of executor returned by the stream's `get_executor` member function.
* For operations which complete immediately (i.e. without calling an
intermediate initiating function), forgetting to use __post__ to
invoke the final handler. This breaks the following initiating
function guarantee: ['Regardless of whether the asynchronous operation
completes immediately or not, the handler will not be invoked from
within this function. Invocation of the handler will be performed
in a manner equivalent to using __post__]. The function
__bind_handler__ is provided for this purpose.
The listing for a complete, runnable version of this example is in
[path_link example/echo-op/echo_op.cpp echo_op.cpp].
[endsect]