echo_op.cpp 15 KB

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