1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
|
//
// 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
//
#include <boost/beast/core.hpp>
#include <boost/asio.hpp>
#include <cstddef>
#include <iostream>
#include <memory>
#include <utility>
namespace net = boost::asio;
namespace beast = boost::beast;
//[example_core_echo_op_1
template<
class AsyncStream,
class DynamicBuffer,
class CompletionToken>
auto
async_echo (AsyncStream& stream, DynamicBuffer& buffer, CompletionToken&& token)
//]
->
typename net::async_result<
typename std::decay<CompletionToken>::type,
void(beast::error_code)>::return_type;
//------------------------------------------------------------------------------
//[example_core_echo_op_2
/** Asynchronously read a line and echo it back.
This function is used to asynchronously read a line ending
in a newline (`"\n"`) from the stream, and then write
it back.
This call always returns immediately. The asynchronous operation
will continue until one of the following conditions is true:
@li A line was read in and written back on the stream
@li An error occurs.
The algorithm, known as a <em>composed asynchronous operation</em>,
is implemented in terms of calls to the stream's `async_read_some`
and `async_write_some` function. The program must ensure that no
other reads or writes are performed until this operation completes.
Since the length of the line is not known ahead of time, the
implementation may read additional characters that lie past the
first line. These characters are stored in the dynamic buffer_.
The same dynamic buffer must be presented again in each call,
to provide the implementation with any leftover bytes.
@param stream The stream to operate on. The type must meet the
requirements of <em>AsyncReadStream</em> and @AsyncWriteStream
@param buffer A dynamic buffer to hold implementation-defined
temporary data. Ownership is not transferred; the caller is
responsible for ensuring that the lifetime of this object is
extended least until the completion handler is invoked.
@param token The handler to be called when the operation completes.
The implementation takes ownership of the handler by performing a decay-copy.
The handler must be invocable with this signature:
@code
void handler(
beast::error_code error // Result of operation.
);
@endcode
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
`net::post`.
*/
template<
class AsyncStream,
class DynamicBuffer,
class CompletionToken>
auto
async_echo (
AsyncStream& stream,
DynamicBuffer& buffer, /*< Unlike Asio, we pass by non-const reference instead of rvalue-ref >*/
CompletionToken&& token) ->
typename net::async_result< /*< `async_result` deduces the return type from the completion handler >*/
typename std::decay<CompletionToken>::type,
void(beast::error_code) /*< The completion handler signature goes here >*/
>::return_type;
//]
//[example_core_echo_op_3
template<class AsyncStream, class Handler>
class echo_op;
// This example uses the Asio's stackless "fauxroutines", implemented
// using a macro-based solution. It makes the code easier to write and
// easier to read. This include file defines the necessary macros and types.
#include <boost/asio/yield.hpp>
// Read a line and echo it back
//
template<
class AsyncStream,
class DynamicBuffer,
class CompletionToken>
auto
async_echo(
AsyncStream& stream,
DynamicBuffer& buffer,
CompletionToken&& token) ->
typename net::async_result<
typename std::decay<CompletionToken>::type,
void(beast::error_code)>::return_type /*< The completion handler signature goes here >*/
{
// Perform some type checks using static assert, this helps
// with more friendly error messages when passing the wrong types.
static_assert(
beast::is_async_stream<AsyncStream>::value,
"AsyncStream type requirements not met");
static_assert(
net::is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer type requirements not met");
// This class template deduces the actual handler type from a
// CompletionToken, captures a local reference to the handler,
// and creates the `async_result` object which becomes the
// return value of this initiating function.
net::async_completion<CompletionToken, void(beast::error_code)> init(token);
// The helper macro BOOST_ASIO_HANDLER_TYPE converts the completion
// token type into a concrete handler type of the correct signature.
using handler_type = BOOST_ASIO_HANDLER_TYPE(CompletionToken, void(beast::error_code));
// The class template `async_base` holds the caller's completion
// handler for us, and provides all of the boilerplate for forwarding
// the associated allocator and associated executor from the caller's
// handler to our operation. It also maintains a `net::executor_work_guard`
// for the executor associated with the stream. This work guard is
// inexpensive, and prevents the execution context from running out
// of work. It is usually necessary although rarely it can be skipped
// depending on the operation (this echo example needs it because it
// performs more than one asynchronous operation in a row).
// We declare this type alias to make the code easier to read.
using base_type = beast::async_base<
handler_type, /*< The type of the completion handler obtained from the token >*/
beast::executor_type<AsyncStream> /*< The type of executor used by the stream to dispatch asynchronous operations >*/
>;
// This nested class implements the echo composed operation as a
// stateful completion handler. We derive from `async_base` to
// take care of boilerplate and we derived from asio::coroutine to
// allow the reenter and yield keywords to work.
struct echo_op : base_type, boost::asio::coroutine
{
AsyncStream& stream_;
DynamicBuffer& buffer_;
echo_op(
AsyncStream& stream,
DynamicBuffer& buffer,
handler_type&& handler)
: base_type(
std::move(handler), /*< The `async_base` helper takes ownership of the handler, >*/
stream.get_executor()) /*< and also needs to know which executor to use. >*/
, stream_(stream)
, buffer_(buffer)
{
// Launch the operation directly from the constructor. We
// pass `false` for `cont` to indicate that the calling
// thread does not represent a continuation of our
// asynchronous control flow.
(*this)({}, 0, false);
}
// If a newline is present in the buffer sequence, this function returns
// the number of characters from the beginning of the buffer up to the
// newline, including the newline character. Otherwise it returns zero.
std::size_t
find_newline(typename DynamicBuffer::const_buffers_type const& buffers)
{
// The `buffers_iterator` class template provides random-access
// iterators into a buffer sequence. Use the standard algorithm
// to look for the new line if it exists.
auto begin = net::buffers_iterator<
typename DynamicBuffer::const_buffers_type>::begin(buffers);
auto end = net::buffers_iterator<
typename DynamicBuffer::const_buffers_type>::end(buffers);
auto result = std::find(begin, end, '\n');
if(result == end)
return 0; // not found
return result + 1 - begin;
}
// This is the entry point of our completion handler. Every time an
// asynchronous operation completes, this function will be invoked.
void
operator()(
beast::error_code ec,
std::size_t bytes_transferred = 0,
bool cont = true) /*< Second and subsequent invocations will seee `cont=true`. */
{
// The `reenter` keyword transfers control to the last
// yield point, or to the beginning of the scope if
// this is the first time.
reenter(*this)
{
for(;;)
{
std::size_t pos;
// Search for a newline in the readable bytes of the buffer
pos = find_newline(buffer_.data());
// If we don't have the newline, then read more
if(pos == 0)
{
std::size_t bytes_to_read;
// Determine the number of bytes to read,
// using available capacity in the buffer first.
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 some data into our dynamic buffer_. We transfer
// ownership of the composed operation by using the
// `std::move(*this)` idiom. The `yield` keyword causes
// the function to return immediately after the initiating
// function returns.
yield stream_.async_read_some(
buffer_.prepare(bytes_to_read), std::move(*this));
// After the `async_read_some` completes, control is
// transferred to this line by the `reenter` keyword.
// Move the bytes read from the writable area to the
// readable area.
buffer_.commit(bytes_transferred);
// If an error occurs, deliver it to the caller's completion handler.
if(ec)
break;
// Keep looping until we get the newline
continue;
}
// We have our newline, so send the first `pos` bytes of the
// buffers. The function `buffers_prefix` returns the front part
// of the buffers we want.
yield net::async_write(stream_,
beast::buffers_prefix(pos, buffer_.data()), std::move(*this));
// After the `async_write` completes, our completion handler will
// be invoked with the error and the number of bytes transferred,
// and the `reenter` statement above will cause control to jump
// to the following line. The variable `pos` is no longer valid
// (remember that we returned from the function using `yield` above)
// but we can use `bytes_transferred` to know how much of the buffer
// to consume. With "real" coroutines this will be easier and more
// natural.
buffer_.consume(bytes_transferred);
// The loop terminates here, and we will either deliver a
// successful result or an error to the caller's completion handler.
break;
}
// When a composed operation completes immediately, it must not
// directly invoke the completion handler otherwise it could
// lead to unfairness, starvation, or stack overflow. Therefore,
// if cont == false (meaning, that the call stack still includes
// the frame of the initiating function) then we need to use
// `net::post` to cause us to be called again after the initiating
// function. The function `async_base::invoke` takes care of
// calling the final completion handler, using post if the
// first argument is false, otherwise invoking it directly.
this->complete(cont, ec);
}
}
};
// Create the composed operation and launch it. This is a constructor
// call followed by invocation of operator(). We use BOOST_ASIO_HANDLER_TYPE
// to convert the completion token into the correct handler type,
// allowing user-defined specializations of the async_result template
// to be used.
echo_op(stream, buffer, std::move(init.completion_handler));
// This hook lets the caller see a return value when appropriate.
// For example this might return std::future<error_code> if
// CompletionToken is net::use_future, or this might
// return an error code if CompletionToken specifies a coroutine.
return init.result.get();
}
// Including this file undefines the macros used by the stackless fauxroutines.
#include <boost/asio/unyield.hpp>
//]
struct move_only_handler
{
move_only_handler() = default;
move_only_handler(move_only_handler&&) = default;
move_only_handler(move_only_handler const&) = delete;
void operator()(beast::error_code ec)
{
if(ec)
std::cerr << ": " << ec.message() << std::endl;
}
};
int main(int argc, char** argv)
{
if(argc != 3)
{
std::cerr
<< "Usage: echo-op <address> <port>\n"
<< "Example:\n"
<< " echo-op 0.0.0.0 8080\n";
return EXIT_FAILURE;
}
namespace net = boost::asio;
auto const address = net::ip::make_address(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
using endpoint_type = net::ip::tcp::endpoint;
// Create a listening socket, accept a connection, perform
// the echo, and then shut everything down and exit.
net::io_context ioc;
net::ip::tcp::acceptor acceptor{ioc};
endpoint_type ep(address, port);
acceptor.open(ep.protocol());
acceptor.set_option(net::socket_base::reuse_address(true));
acceptor.bind(ep);
acceptor.listen();
auto sock = acceptor.accept();
beast::flat_buffer buffer;
async_echo(sock, buffer, move_only_handler{});
ioc.run();
return EXIT_SUCCESS;
}
|