]>
Commit | Line | Data |
---|---|---|
b32b8144 | 1 | // |
92f5a8d4 | 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) |
b32b8144 FG |
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 | ||
92f5a8d4 TL |
17 | namespace net = boost::asio; |
18 | namespace beast = boost::beast; | |
19 | ||
b32b8144 FG |
20 | //[example_core_echo_op_1 |
21 | ||
22 | template< | |
23 | class AsyncStream, | |
92f5a8d4 | 24 | class DynamicBuffer, |
b32b8144 FG |
25 | class CompletionToken> |
26 | auto | |
92f5a8d4 | 27 | async_echo (AsyncStream& stream, DynamicBuffer& buffer, CompletionToken&& token) |
b32b8144 FG |
28 | |
29 | //] | |
92f5a8d4 TL |
30 | -> |
31 | typename net::async_result< | |
32 | typename std::decay<CompletionToken>::type, | |
33 | void(beast::error_code)>::return_type; | |
34 | ||
35 | //------------------------------------------------------------------------------ | |
b32b8144 FG |
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 | |
92f5a8d4 TL |
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: | |
b32b8144 | 47 | |
92f5a8d4 | 48 | @li A line was read in and written back on the stream |
b32b8144 FG |
49 | |
50 | @li An error occurs. | |
51 | ||
92f5a8d4 TL |
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 | |
b32b8144 | 65 | |
92f5a8d4 TL |
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. | |
b32b8144 | 70 | |
92f5a8d4 TL |
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: | |
b32b8144 FG |
74 | @code |
75 | void handler( | |
92f5a8d4 | 76 | beast::error_code error // Result of operation. |
b32b8144 FG |
77 | ); |
78 | @endcode | |
92f5a8d4 TL |
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`. | |
b32b8144 FG |
84 | */ |
85 | template< | |
86 | class AsyncStream, | |
92f5a8d4 | 87 | class DynamicBuffer, |
b32b8144 | 88 | class CompletionToken> |
92f5a8d4 TL |
89 | auto |
90 | async_echo ( | |
b32b8144 | 91 | AsyncStream& stream, |
92f5a8d4 TL |
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; | |
b32b8144 FG |
98 | //] |
99 | ||
92f5a8d4 | 100 | //[example_core_echo_op_3 |
b32b8144 | 101 | |
b32b8144 | 102 | template<class AsyncStream, class Handler> |
92f5a8d4 TL |
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 | |
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 >*/ | |
b32b8144 | 124 | { |
92f5a8d4 TL |
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 | |
b32b8144 | 168 | { |
92f5a8d4 TL |
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) | |
b32b8144 | 181 | { |
92f5a8d4 TL |
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); | |
b32b8144 | 187 | } |
b32b8144 | 188 | |
92f5a8d4 TL |
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. | |
b32b8144 | 192 | |
92f5a8d4 TL |
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. | |
b32b8144 | 199 | |
92f5a8d4 TL |
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'); | |
b32b8144 | 205 | |
92f5a8d4 TL |
206 | if(result == end) |
207 | return 0; // not found | |
b32b8144 | 208 | |
92f5a8d4 TL |
209 | return result + 1 - begin; |
210 | } | |
b32b8144 | 211 | |
92f5a8d4 TL |
212 | // This is the entry point of our completion handler. Every time an |
213 | // asynchronous operation completes, this function will be invoked. | |
b32b8144 | 214 | |
92f5a8d4 TL |
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 | }; | |
b32b8144 FG |
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. | |
92f5a8d4 TL |
317 | |
318 | echo_op(stream, buffer, std::move(init.completion_handler)); | |
b32b8144 FG |
319 | |
320 | // This hook lets the caller see a return value when appropriate. | |
321 | // For example this might return std::future<error_code> if | |
92f5a8d4 | 322 | // CompletionToken is net::use_future, or this might |
b32b8144 | 323 | // return an error code if CompletionToken specifies a coroutine. |
92f5a8d4 | 324 | |
b32b8144 FG |
325 | return init.result.get(); |
326 | } | |
327 | ||
92f5a8d4 TL |
328 | // Including this file undefines the macros used by the stackless fauxroutines. |
329 | #include <boost/asio/unyield.hpp> | |
330 | ||
b32b8144 FG |
331 | //] |
332 | ||
92f5a8d4 TL |
333 | struct move_only_handler |
334 | { | |
f67539c2 | 335 | move_only_handler() = default; |
92f5a8d4 TL |
336 | move_only_handler(move_only_handler&&) = default; |
337 | move_only_handler(move_only_handler const&) = delete; | |
338 | ||
339 | void operator()(beast::error_code ec) | |
340 | { | |
341 | if(ec) | |
342 | std::cerr << ": " << ec.message() << std::endl; | |
343 | } | |
344 | }; | |
345 | ||
346 | int main(int argc, char** argv) | |
b32b8144 | 347 | { |
92f5a8d4 TL |
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; | |
b32b8144 FG |
362 | |
363 | // Create a listening socket, accept a connection, perform | |
364 | // the echo, and then shut everything down and exit. | |
92f5a8d4 TL |
365 | net::io_context ioc; |
366 | net::ip::tcp::acceptor acceptor{ioc}; | |
367 | endpoint_type ep{address, port}; | |
b32b8144 | 368 | acceptor.open(ep.protocol()); |
92f5a8d4 | 369 | acceptor.set_option(net::socket_base::reuse_address(true)); |
b32b8144 FG |
370 | acceptor.bind(ep); |
371 | acceptor.listen(); | |
92f5a8d4 TL |
372 | auto sock = acceptor.accept(); |
373 | beast::flat_buffer buffer; | |
374 | async_echo(sock, buffer, move_only_handler{}); | |
b32b8144 | 375 | ioc.run(); |
92f5a8d4 | 376 | return EXIT_SUCCESS; |
b32b8144 | 377 | } |