]>
Commit | Line | Data |
---|---|---|
b32b8144 FG |
1 | // |
2 | // Copyright (c) 2016-2017 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 | //[example_core_echo_op_1 | |
18 | ||
19 | template< | |
20 | class AsyncStream, | |
21 | class CompletionToken> | |
22 | auto | |
23 | async_echo(AsyncStream& stream, CompletionToken&& token) | |
24 | ||
25 | //] | |
26 | -> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::beast::error_code)); | |
27 | ||
28 | //[example_core_echo_op_2 | |
29 | ||
30 | /** Asynchronously read a line and echo it back. | |
31 | ||
32 | This function is used to asynchronously read a line ending | |
33 | in a carriage-return ("CR") from the stream, and then write | |
34 | it back. The function call always returns immediately. The | |
35 | asynchronous operation will continue until one of the | |
36 | following conditions is true: | |
37 | ||
38 | @li A line was read in and sent back on the stream | |
39 | ||
40 | @li An error occurs. | |
41 | ||
42 | This operation is implemented in terms of one or more calls to | |
43 | the stream's `async_read_some` and `async_write_some` functions, | |
44 | and is known as a <em>composed operation</em>. The program must | |
45 | ensure that the stream performs no other operations until this | |
46 | operation completes. The implementation may read additional octets | |
47 | that lie past the end of the line being read. These octets are | |
48 | silently discarded. | |
49 | ||
50 | @param The stream to operate on. The type must meet the | |
51 | requirements of @b AsyncReadStream and @AsyncWriteStream | |
52 | ||
53 | @param token The completion token to use. If this is a | |
54 | completion handler, copies will be made as required. | |
55 | The equivalent signature of the handler must be: | |
56 | @code | |
57 | void handler( | |
58 | error_code ec // result of operation | |
59 | ); | |
60 | @endcode | |
61 | Regardless of whether the asynchronous operation completes | |
62 | immediately or not, the handler will not be invoked from within | |
63 | this function. Invocation of the handler will be performed in a | |
64 | manner equivalent to using `boost::asio::io_context::post`. | |
65 | */ | |
66 | template< | |
67 | class AsyncStream, | |
68 | class CompletionToken> | |
69 | BOOST_ASIO_INITFN_RESULT_TYPE( /*< `BOOST_ASIO_INITFN_RESULT_TYPE` customizes the return value based on the completion token >*/ | |
70 | CompletionToken, | |
71 | void(boost::beast::error_code)) /*< This is the signature for the completion handler >*/ | |
72 | async_echo( | |
73 | AsyncStream& stream, | |
74 | CompletionToken&& token); | |
75 | ||
76 | //] | |
77 | ||
78 | //[example_core_echo_op_4 | |
79 | ||
80 | // This composed operation reads a line of input and echoes it back. | |
81 | // | |
82 | template<class AsyncStream, class Handler> | |
83 | class echo_op | |
84 | { | |
85 | // This holds all of the state information required by the operation. | |
86 | struct state | |
87 | { | |
88 | // The stream to read and write to | |
89 | AsyncStream& stream; | |
90 | ||
91 | // Indicates what step in the operation's state machine | |
92 | // to perform next, starting from zero. | |
93 | int step = 0; | |
94 | ||
95 | // The buffer used to hold the input and output data. | |
96 | // | |
97 | // We use a custom allocator for performance, this allows | |
98 | // the implementation of the io_context to make efficient | |
99 | // re-use of memory allocated by composed operations during | |
100 | // a continuation. | |
101 | // | |
102 | boost::asio::basic_streambuf<typename std::allocator_traits< | |
103 | boost::asio::associated_allocator_t<Handler> >:: | |
104 | template rebind_alloc<char> > buffer; | |
105 | ||
106 | // handler_ptr requires that the first parameter to the | |
107 | // contained object constructor is a reference to the | |
108 | // managed final completion handler. | |
109 | // | |
11fdf7f2 | 110 | explicit state(Handler const& handler, AsyncStream& stream_) |
b32b8144 FG |
111 | : stream(stream_) |
112 | , buffer((std::numeric_limits<std::size_t>::max)(), | |
113 | boost::asio::get_associated_allocator(handler)) | |
114 | { | |
115 | } | |
116 | }; | |
117 | ||
118 | // The operation's data is kept in a cheap-to-copy smart | |
119 | // pointer container called `handler_ptr`. This efficiently | |
120 | // satisfies the CopyConstructible requirements of completion | |
121 | // handlers with expensive-to-copy state. | |
122 | // | |
123 | // `handler_ptr` uses the allocator associated with the final | |
124 | // completion handler, in order to allocate the storage for `state`. | |
125 | // | |
126 | boost::beast::handler_ptr<state, Handler> p_; | |
127 | ||
128 | public: | |
129 | // Boost.Asio requires that handlers are CopyConstructible. | |
130 | // In some cases, it takes advantage of handlers that are | |
131 | // MoveConstructible. This operation supports both. | |
132 | // | |
133 | echo_op(echo_op&&) = default; | |
134 | echo_op(echo_op const&) = default; | |
135 | ||
136 | // The constructor simply creates our state variables in | |
137 | // the smart pointer container. | |
138 | // | |
139 | template<class DeducedHandler, class... Args> | |
140 | echo_op(AsyncStream& stream, DeducedHandler&& handler) | |
141 | : p_(std::forward<DeducedHandler>(handler), stream) | |
142 | { | |
143 | } | |
144 | ||
145 | // Associated allocator support. This is Asio's system for | |
146 | // allowing the final completion handler to customize the | |
147 | // memory allocation strategy used for composed operation | |
148 | // states. A composed operation should use the same allocator | |
149 | // as the final handler. These declarations achieve that. | |
150 | ||
151 | using allocator_type = | |
152 | boost::asio::associated_allocator_t<Handler>; | |
153 | ||
154 | allocator_type | |
155 | get_allocator() const noexcept | |
156 | { | |
11fdf7f2 | 157 | return (boost::asio::get_associated_allocator)(p_.handler()); |
b32b8144 FG |
158 | } |
159 | ||
160 | // Executor hook. This is Asio's system for customizing the | |
161 | // manner in which asynchronous completion handlers are invoked. | |
162 | // A composed operation needs to use the same executor to invoke | |
163 | // intermediate completion handlers as that used to invoke the | |
164 | // final handler. | |
165 | ||
166 | using executor_type = boost::asio::associated_executor_t< | |
167 | Handler, decltype(std::declval<AsyncStream&>().get_executor())>; | |
168 | ||
169 | executor_type get_executor() const noexcept | |
170 | { | |
11fdf7f2 | 171 | return (boost::asio::get_associated_executor)( |
b32b8144 FG |
172 | p_.handler(), p_->stream.get_executor()); |
173 | } | |
174 | ||
175 | // The entry point for this handler. This will get called | |
176 | // as our intermediate operations complete. Definition below. | |
177 | // | |
178 | void operator()(boost::beast::error_code ec, std::size_t bytes_transferred); | |
179 | }; | |
180 | ||
181 | //] | |
182 | ||
183 | //[example_core_echo_op_5 | |
184 | ||
185 | // echo_op is callable with the signature void(error_code, bytes_transferred), | |
186 | // allowing `*this` to be used as both a ReadHandler and a WriteHandler. | |
187 | // | |
188 | template<class AsyncStream, class Handler> | |
189 | void echo_op<AsyncStream, Handler>:: | |
190 | operator()(boost::beast::error_code ec, std::size_t bytes_transferred) | |
191 | { | |
192 | // Store a reference to our state. The address of the state won't | |
193 | // change, and this solves the problem where dereferencing the | |
194 | // data member is undefined after a move. | |
195 | auto& p = *p_; | |
196 | ||
197 | // Now perform the next step in the state machine | |
198 | switch(ec ? 2 : p.step) | |
199 | { | |
200 | // initial entry | |
201 | case 0: | |
202 | // read up to the first newline | |
203 | p.step = 1; | |
204 | return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this)); | |
205 | ||
206 | case 1: | |
207 | // write everything back | |
208 | p.step = 2; | |
209 | // async_read_until could have read past the newline, | |
210 | // use buffers_prefix to make sure we only send one line | |
211 | return boost::asio::async_write(p.stream, | |
212 | boost::beast::buffers_prefix(bytes_transferred, p.buffer.data()), std::move(*this)); | |
213 | ||
214 | case 2: | |
215 | p.buffer.consume(bytes_transferred); | |
216 | break; | |
217 | } | |
218 | ||
219 | // Invoke the final handler. The implementation of `handler_ptr` | |
220 | // will deallocate the storage for the state before the handler | |
221 | // is invoked. This is necessary to provide the | |
222 | // destroy-before-invocation guarantee on handler memory | |
223 | // customizations. | |
224 | // | |
225 | // If we wanted to pass any arguments to the handler which come | |
226 | // from the `state`, they would have to be moved to the stack | |
227 | // first or else undefined behavior results. | |
228 | // | |
229 | p_.invoke(ec); | |
230 | return; | |
231 | } | |
232 | ||
233 | //] | |
234 | ||
235 | //[example_core_echo_op_3 | |
236 | ||
237 | template<class AsyncStream, class Handler> | |
238 | class echo_op; | |
239 | ||
240 | // Read a line and echo it back | |
241 | // | |
242 | template<class AsyncStream, class CompletionToken> | |
243 | BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::beast::error_code)) | |
244 | async_echo(AsyncStream& stream, CompletionToken&& token) | |
245 | { | |
246 | // Make sure stream meets the requirements. We use static_assert | |
247 | // to cause a friendly message instead of an error novel. | |
248 | // | |
249 | static_assert(boost::beast::is_async_stream<AsyncStream>::value, | |
250 | "AsyncStream requirements not met"); | |
251 | ||
252 | // This helper manages some of the handler's lifetime and | |
253 | // uses the result and handler specializations associated with | |
254 | // the completion token to help customize the return value. | |
255 | // | |
256 | boost::asio::async_completion<CompletionToken, void(boost::beast::error_code)> init{token}; | |
257 | ||
258 | // Create the composed operation and launch it. This is a constructor | |
259 | // call followed by invocation of operator(). We use BOOST_ASIO_HANDLER_TYPE | |
260 | // to convert the completion token into the correct handler type, | |
261 | // allowing user-defined specializations of the async_result template | |
262 | // to be used. | |
263 | // | |
264 | echo_op< | |
265 | AsyncStream, | |
266 | BOOST_ASIO_HANDLER_TYPE( | |
267 | CompletionToken, void(boost::beast::error_code))>{ | |
268 | stream, | |
269 | init.completion_handler}(boost::beast::error_code{}, 0); | |
270 | ||
271 | // This hook lets the caller see a return value when appropriate. | |
272 | // For example this might return std::future<error_code> if | |
273 | // CompletionToken is boost::asio::use_future, or this might | |
274 | // return an error code if CompletionToken specifies a coroutine. | |
275 | // | |
276 | return init.result.get(); | |
277 | } | |
278 | ||
279 | //] | |
280 | ||
281 | int main(int, char** argv) | |
282 | { | |
283 | using socket_type = boost::asio::ip::tcp::socket; | |
284 | using endpoint_type = boost::asio::ip::tcp::endpoint; | |
285 | ||
286 | // Create a listening socket, accept a connection, perform | |
287 | // the echo, and then shut everything down and exit. | |
288 | boost::asio::io_context ioc; | |
289 | socket_type sock{ioc}; | |
290 | boost::asio::ip::tcp::acceptor acceptor{ioc}; | |
291 | endpoint_type ep{boost::asio::ip::make_address("0.0.0.0"), 0}; | |
292 | acceptor.open(ep.protocol()); | |
293 | acceptor.bind(ep); | |
294 | acceptor.listen(); | |
295 | acceptor.accept(sock); | |
296 | async_echo(sock, | |
297 | [&](boost::beast::error_code ec) | |
298 | { | |
299 | if(ec) | |
300 | std::cerr << argv[0] << ": " << ec.message() << std::endl; | |
301 | }); | |
302 | ioc.run(); | |
303 | return 0; | |
304 | } |