222 lines
9.2 KiB
Plaintext
222 lines
9.2 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:timeouts Timeouts __example__]
|
|
|
|
Network programs must handle adverse connection conditions; the most common
|
|
is that a connected peer goes offline unexpectedly. Protocols have no way of
|
|
identifying this reliably: the peer is offline after all, and unable to send
|
|
a message announcing the absence. A peer can go offline for various reasons:
|
|
|
|
[itemized_list
|
|
[The peer experiences a power loss]
|
|
[The peer becomes disconnected from the network]
|
|
[The local host becomes disconnected from the network]
|
|
[The network itself becomes unavailable]
|
|
]
|
|
|
|
To determine when a peer is offline or idle, a program will implement a
|
|
[@https://en.wikipedia.org/wiki/Timeout_(computing) timeout]
|
|
algorithm, which closes the connection after a specified amount of time if
|
|
some condition is met. For example, if no data is received for the duration.
|
|
A timeout may be used to:
|
|
|
|
[itemized_list
|
|
[Drop malicious or poorly performing hosts]
|
|
[Close idle connections to free up resources]
|
|
[Determine if a peer is offline or no longer available]
|
|
]
|
|
|
|
Traditionally, programs use a
|
|
[@boost:/doc/html/boost_asio/reference/steady_timer.html `net::steady_timer`]
|
|
to determine when a timeout occurs, and then call
|
|
[@boost:/doc/html/boost_asio/reference/basic_socket/close/overload2.html `close`]
|
|
on the socket to release the resources. The complexity of managing a separate
|
|
timer is often a source of
|
|
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1269r0.html#timers frustration]
|
|
for non-experts.
|
|
|
|
[note
|
|
For portability reasons, networking does not provide timeouts
|
|
or cancellation features for synchronous stream operations.
|
|
]
|
|
|
|
To simplify the handling of timeouts, these provided types wrap a
|
|
[@boost:/doc/html/boost_asio/reference/basic_stream_socket.html `net::basic_stream_socket`]
|
|
to provide additional features:
|
|
|
|
[table
|
|
[[Name][Features]]
|
|
[
|
|
[[link beast.ref.boost__beast__tcp_stream `tcp_stream`]]
|
|
[[itemized_list
|
|
[Timeouts for logical operations]
|
|
[[@boost:/doc/html/boost_asio/reference/ip__tcp.html `net::ip::tcp`] protocol]
|
|
[[@boost:/doc/html/boost_asio/reference/executor.html `net::executor`] executor]
|
|
[[link beast.ref.boost__beast__unlimited_rate_policy `unlimited_rate_policy`] rate limits]
|
|
]]
|
|
][
|
|
[[link beast.ref.boost__beast__basic_stream `basic_stream`]]
|
|
[[itemized_list
|
|
[Timeouts for logical operations]
|
|
[Configurable __Protocol__ type]
|
|
[Configurable __Executor__ type]
|
|
[Configurable __RatePolicy__ type]
|
|
]]
|
|
]]
|
|
|
|
[/-----------------------------------------------------------------------------]
|
|
|
|
[heading Construction]
|
|
|
|
The `tcp_stream` is designed as a replacement for
|
|
[@boost:/doc/html/boost_asio/reference/ip__tcp/socket.html `net::ip::tcp::socket`].
|
|
Any program which currently uses a socket, can switch to a `tcp_stream` and achieve
|
|
the features above (although some interfaces are different, see below).
|
|
Networking now allows I/O objects to construct with any instance of
|
|
__ExecutionContext__ or __Executor__ objects. Here we construct a stream which
|
|
uses a particular I/O context to dispatch completion handlers:
|
|
|
|
[code_core_3_timeouts_1]
|
|
|
|
Alternatively, we can construct the stream from an executor:
|
|
|
|
[code_core_3_timeouts_2]
|
|
|
|
The function
|
|
[@boost:/doc/html/boost_asio/reference/make_strand.html `make_strand`]
|
|
returns a strand constructed from an execution context or executor. When a
|
|
[@boost:/doc/html/boost_asio/reference/strand.html `net::strand`]
|
|
is chosen for the stream's executor, all completion handlers which do not
|
|
already have an associated executor will use the strand. This is both a
|
|
notational convenience (no need for `strand::wrap` or `bind_executor` at
|
|
call sites) and a measure of safety, as it is no longer possible to forget
|
|
to use the strand.
|
|
|
|
[code_core_3_timeouts_3]
|
|
|
|
[/-----------------------------------------------------------------------------]
|
|
|
|
[heading Connecting]
|
|
|
|
Before data can be exchanged, the stream needs to be connected to a peer.
|
|
The following code sets a timeout for an asynchronous connect operation.
|
|
In Beast, functions to connect to a range of endpoints (such as the range
|
|
returned by
|
|
[@boost:/doc/html/boost_asio/reference/ip__basic_resolver/resolve/overload3.html `net::ip::tcp::resolver::resolve`])
|
|
are members of the class rather than free functions such as
|
|
[@boost:/doc/html/boost_asio/reference/async_connect.html `net::async_connect`].
|
|
|
|
[code_core_3_timeouts_4]
|
|
|
|
A server will use an acceptor bound to a particular IP address and port to
|
|
listen to and receive incoming connection requests. The acceptor returns
|
|
an ordinary socket. A `tcp_stream` can be move-constructed from the
|
|
underlying `basic_stream_socket` thusly:
|
|
|
|
[code_core_3_timeouts_5]
|
|
|
|
[/-----------------------------------------------------------------------------]
|
|
|
|
[heading Reading and Writing]
|
|
|
|
Timeouts apply to the logical operation, expressed as a series of asynchronous
|
|
calls, rather than just the next call. This code reads a line from the stream
|
|
and writes it back. Both the read and the write must complete within 30 seconds
|
|
from when the timeout was set; the timer is not reset between operations.
|
|
|
|
[code_core_3_timeouts_6]
|
|
|
|
Since reads and writes can take place concurrently, it is possible to have
|
|
two simultaneous logical operations where each operation either only reads,
|
|
or only writes. The beginning of a new read or write operation will use
|
|
the most recently set timeout. This will not affect operations that are
|
|
already outstanding.
|
|
|
|
[code_core_3_timeouts_7]
|
|
|
|
When a timeout is set, it cancels any previous read or write timeout for which
|
|
no outstanding operation is in progress. Algorithms which loop over logical
|
|
operations simply need to set the timeout once before the logical operation,
|
|
it is not necessary to call `expires_never` in this case. Here we implement
|
|
an algorithm which continuously echoes lines back, with a timeout. This example
|
|
is implemented as a complete function.
|
|
|
|
[code_core_3_timeouts_1f]
|
|
|
|
[/-----------------------------------------------------------------------------]
|
|
|
|
[heading https_get]
|
|
|
|
It is important to note that all of the examples thus far which perform
|
|
reads and writes with a timeout, make use of the existing networking stream
|
|
algorithms. As these algorithms are written generically to work with any
|
|
object meeting the stream requirements, they transparently support timeouts
|
|
when used with `tcp_stream`. This can be used to enable timeouts for stream
|
|
wrappers that do not currently support timeouts.
|
|
|
|
The following code establishes an encrypted connection, writes an HTTP
|
|
request, reads the HTTP response, and closes the connection gracefully.
|
|
If these operations take longer than 30 seconds total, a timeout occurs.
|
|
This code is intended to show how `tcp_stream` can be used to enable
|
|
timeouts across unmodified stream algorithms which were not originally
|
|
written to support timing out, and how a blocking algorithm may be written
|
|
from asynchronous intermediate operations.
|
|
|
|
[code_core_3_timeouts_2f]
|
|
|
|
[endsect]
|
|
|
|
[/-----------------------------------------------------------------------------]
|
|
|
|
[section:rate_limiting Rate Limiting __example__]
|
|
|
|
The
|
|
[link beast.ref.boost__beast__basic_stream `basic_stream`]
|
|
class template supports an additional `RatePolicy` template parameter. Objects
|
|
of this type must meet the requirements of __RatePolicy__. They are used to
|
|
implement rate limiting or bandwidth management. The default policy for
|
|
`basic_stream` and `tcp_stream` is
|
|
[link beast.ref.boost__beast__unlimited_rate_policy `unlimited_rate_policy`],
|
|
which places no limits on reading and writing. The library comes with the
|
|
[link beast.ref.boost__beast__simple_rate_policy `simple_rate_policy`],
|
|
allowing for independent control of read and write limits expressed in terms
|
|
of bytes per second. The follow code creates an instance of the basic stream
|
|
with a simple rate policy, and sets the read and write limits:
|
|
|
|
[code_core_3_timeouts_8]
|
|
|
|
More sophisticated rate policies can be implemented as user-defined types which
|
|
meet the requirements of __RatePolicy__. Here, we develop a rate policy that
|
|
measures the instantaneous throughput of reads and writes. First we write a
|
|
small utility class that applies an exponential smoothing function to a series
|
|
of discrete rate samples, to calculate instantaneous throughput.
|
|
|
|
[code_core_3_timeouts_3f]
|
|
|
|
Then we define our rate policy object. We friend the type
|
|
[link beast.ref.boost__beast__rate_policy_access `rate_policy_access`] to
|
|
allow our implementation to be private, but still allow the `basic_stream`
|
|
access to call the required functions. This lets us avoid having to write
|
|
a cumbersome friend declaration for the `basic_stream` class template.
|
|
Public members of rate policy objects become part of the stream object's
|
|
interface, through a call to `rate_policy`.
|
|
|
|
[code_core_3_timeouts_4f]
|
|
|
|
To use our new policy we declare an instance of the stream, and then use it
|
|
with stream algorithms as usual. At any time, we can determine the current
|
|
read or write rates by calling into the policy.
|
|
|
|
[code_core_3_timeouts_9]
|
|
|
|
[endsect]
|
|
|
|
[/-----------------------------------------------------------------------------]
|