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