]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // |
2 | // composed_6.cpp | |
3 | // ~~~~~~~~~~~~~~ | |
4 | // | |
f67539c2 | 5 | // Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
92f5a8d4 TL |
6 | // |
7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
9 | // | |
10 | ||
11 | #include <boost/asio/executor_work_guard.hpp> | |
12 | #include <boost/asio/io_context.hpp> | |
13 | #include <boost/asio/ip/tcp.hpp> | |
14 | #include <boost/asio/steady_timer.hpp> | |
15 | #include <boost/asio/use_future.hpp> | |
16 | #include <boost/asio/write.hpp> | |
17 | #include <functional> | |
18 | #include <iostream> | |
19 | #include <memory> | |
20 | #include <sstream> | |
21 | #include <string> | |
22 | #include <type_traits> | |
23 | #include <utility> | |
24 | ||
25 | using boost::asio::ip::tcp; | |
26 | ||
27 | // NOTE: This example requires the new boost::asio::async_initiate function. For | |
28 | // an example that works with the Networking TS style of completion tokens, | |
29 | // please see an older version of asio. | |
30 | ||
31 | //------------------------------------------------------------------------------ | |
32 | ||
33 | // This composed operation shows composition of multiple underlying operations. | |
34 | // It automatically serialises a message, using its I/O streams insertion | |
35 | // operator, before sending it N times on the socket. To do this, it must | |
36 | // allocate a buffer for the encoded message and ensure this buffer's validity | |
37 | // until all underlying async_write operation complete. A one second delay is | |
38 | // inserted prior to each write operation, using a steady_timer. | |
39 | ||
40 | // In addition to determining the mechanism by which an asynchronous operation | |
41 | // delivers its result, a completion token also determines the time when the | |
42 | // operation commences. For example, when the completion token is a simple | |
43 | // callback the operation commences before the initiating function returns. | |
44 | // However, if the completion token's delivery mechanism uses a future, we | |
45 | // might instead want to defer initiation of the operation until the returned | |
46 | // future object is waited upon. | |
47 | // | |
48 | // To enable this, when implementing an asynchronous operation we must package | |
49 | // the initiation step as a function object. | |
50 | struct async_write_message_initiation | |
51 | { | |
52 | // The initiation function object's call operator is passed the concrete | |
53 | // completion handler produced by the completion token. This completion | |
54 | // handler matches the asynchronous operation's completion handler signature, | |
55 | // which in this example is: | |
56 | // | |
57 | // void(boost::system::error_code error) | |
58 | // | |
59 | // The initiation function object also receives any additional arguments | |
60 | // required to start the operation. (Note: We could have instead passed these | |
61 | // arguments as members in the initiaton function object. However, we should | |
62 | // prefer to propagate them as function call arguments as this allows the | |
63 | // completion token to optimise how they are passed. For example, a lazy | |
64 | // future which defers initiation would need to make a decay-copy of the | |
65 | // arguments, but when using a simple callback the arguments can be trivially | |
66 | // forwarded straight through.) | |
67 | template <typename CompletionHandler> | |
68 | void operator()(CompletionHandler&& completion_handler, tcp::socket& socket, | |
69 | std::unique_ptr<std::string> encoded_message, std::size_t repeat_count, | |
70 | std::unique_ptr<boost::asio::steady_timer> delay_timer) const | |
71 | { | |
72 | // In this example, the composed operation's intermediate completion | |
73 | // handler is implemented as a hand-crafted function object. | |
74 | struct intermediate_completion_handler | |
75 | { | |
76 | // The intermediate completion handler holds a reference to the socket as | |
77 | // it is used for multiple async_write operations, as well as for | |
78 | // obtaining the I/O executor (see get_executor below). | |
79 | tcp::socket& socket_; | |
80 | ||
81 | // The allocated buffer for the encoded message. The std::unique_ptr | |
82 | // smart pointer is move-only, and as a consequence our intermediate | |
83 | // completion handler is also move-only. | |
84 | std::unique_ptr<std::string> encoded_message_; | |
85 | ||
86 | // The repeat count remaining. | |
87 | std::size_t repeat_count_; | |
88 | ||
89 | // A steady timer used for introducing a delay. | |
90 | std::unique_ptr<boost::asio::steady_timer> delay_timer_; | |
91 | ||
92 | // To manage the cycle between the multiple underlying asychronous | |
93 | // operations, our intermediate completion handler is implemented as a | |
94 | // state machine. | |
95 | enum { starting, waiting, writing } state_; | |
96 | ||
97 | // As our composed operation performs multiple underlying I/O operations, | |
98 | // we should maintain a work object against the I/O executor. This tells | |
99 | // the I/O executor that there is still more work to come in the future. | |
20effc67 TL |
100 | typename std::decay<decltype(boost::asio::prefer( |
101 | std::declval<tcp::socket::executor_type>(), | |
102 | boost::asio::execution::outstanding_work.tracked))>::type io_work_; | |
92f5a8d4 TL |
103 | |
104 | // The user-supplied completion handler, called once only on completion | |
105 | // of the entire composed operation. | |
106 | typename std::decay<CompletionHandler>::type handler_; | |
107 | ||
108 | // By having a default value for the second argument, this function call | |
109 | // operator matches the completion signature of both the async_write and | |
110 | // steady_timer::async_wait operations. | |
111 | void operator()(const boost::system::error_code& error, std::size_t = 0) | |
112 | { | |
113 | if (!error) | |
114 | { | |
115 | switch (state_) | |
116 | { | |
117 | case starting: | |
118 | case writing: | |
119 | if (repeat_count_ > 0) | |
120 | { | |
121 | --repeat_count_; | |
122 | state_ = waiting; | |
123 | delay_timer_->expires_after(std::chrono::seconds(1)); | |
124 | delay_timer_->async_wait(std::move(*this)); | |
125 | return; // Composed operation not yet complete. | |
126 | } | |
127 | break; // Composed operation complete, continue below. | |
128 | case waiting: | |
129 | state_ = writing; | |
130 | boost::asio::async_write(socket_, | |
131 | boost::asio::buffer(*encoded_message_), std::move(*this)); | |
132 | return; // Composed operation not yet complete. | |
133 | } | |
134 | } | |
135 | ||
136 | // This point is reached only on completion of the entire composed | |
137 | // operation. | |
138 | ||
92f5a8d4 TL |
139 | // Deallocate the encoded message before calling the user-supplied |
140 | // completion handler. | |
141 | encoded_message_.reset(); | |
142 | ||
143 | // Call the user-supplied handler with the result of the operation. | |
144 | handler_(error); | |
145 | } | |
146 | ||
147 | // It is essential to the correctness of our composed operation that we | |
148 | // preserve the executor of the user-supplied completion handler. With a | |
149 | // hand-crafted function object we can do this by defining a nested type | |
150 | // executor_type and member function get_executor. These obtain the | |
151 | // completion handler's associated executor, and default to the I/O | |
152 | // executor - in this case the executor of the socket - if the completion | |
153 | // handler does not have its own. | |
154 | using executor_type = boost::asio::associated_executor_t< | |
155 | typename std::decay<CompletionHandler>::type, | |
156 | tcp::socket::executor_type>; | |
157 | ||
158 | executor_type get_executor() const noexcept | |
159 | { | |
160 | return boost::asio::get_associated_executor( | |
161 | handler_, socket_.get_executor()); | |
162 | } | |
163 | ||
164 | // Although not necessary for correctness, we may also preserve the | |
165 | // allocator of the user-supplied completion handler. This is achieved by | |
166 | // defining a nested type allocator_type and member function | |
167 | // get_allocator. These obtain the completion handler's associated | |
168 | // allocator, and default to std::allocator<void> if the completion | |
169 | // handler does not have its own. | |
170 | using allocator_type = boost::asio::associated_allocator_t< | |
171 | typename std::decay<CompletionHandler>::type, | |
172 | std::allocator<void>>; | |
173 | ||
174 | allocator_type get_allocator() const noexcept | |
175 | { | |
176 | return boost::asio::get_associated_allocator( | |
177 | handler_, std::allocator<void>{}); | |
178 | } | |
179 | }; | |
180 | ||
181 | // Initiate the underlying async_write operation using our intermediate | |
182 | // completion handler. | |
183 | auto encoded_message_buffer = boost::asio::buffer(*encoded_message); | |
184 | boost::asio::async_write(socket, encoded_message_buffer, | |
185 | intermediate_completion_handler{ | |
186 | socket, std::move(encoded_message), | |
187 | repeat_count, std::move(delay_timer), | |
188 | intermediate_completion_handler::starting, | |
20effc67 TL |
189 | boost::asio::prefer(socket.get_executor(), |
190 | boost::asio::execution::outstanding_work.tracked), | |
92f5a8d4 TL |
191 | std::forward<CompletionHandler>(completion_handler)}); |
192 | } | |
193 | }; | |
194 | ||
195 | template <typename T, typename CompletionToken> | |
196 | auto async_write_messages(tcp::socket& socket, | |
197 | const T& message, std::size_t repeat_count, | |
198 | CompletionToken&& token) | |
199 | // The return type of the initiating function is deduced from the combination | |
200 | // of CompletionToken type and the completion handler's signature. When the | |
201 | // completion token is a simple callback, the return type is always void. | |
202 | // In this example, when the completion token is boost::asio::yield_context | |
203 | // (used for stackful coroutines) the return type would be also be void, as | |
204 | // there is no non-error argument to the completion handler. When the | |
205 | // completion token is boost::asio::use_future it would be std::future<void>. | |
206 | -> typename boost::asio::async_result< | |
207 | typename std::decay<CompletionToken>::type, | |
208 | void(boost::system::error_code)>::return_type | |
209 | { | |
210 | // Encode the message and copy it into an allocated buffer. The buffer will | |
211 | // be maintained for the lifetime of the composed asynchronous operation. | |
212 | std::ostringstream os; | |
213 | os << message; | |
214 | std::unique_ptr<std::string> encoded_message(new std::string(os.str())); | |
215 | ||
216 | // Create a steady_timer to be used for the delay between messages. | |
217 | std::unique_ptr<boost::asio::steady_timer> delay_timer( | |
218 | new boost::asio::steady_timer(socket.get_executor())); | |
219 | ||
220 | // The boost::asio::async_initiate function takes: | |
221 | // | |
222 | // - our initiation function object, | |
223 | // - the completion token, | |
224 | // - the completion handler signature, and | |
225 | // - any additional arguments we need to initiate the operation. | |
226 | // | |
227 | // It then asks the completion token to create a completion handler (i.e. a | |
228 | // callback) with the specified signature, and invoke the initiation function | |
229 | // object with this completion handler as well as the additional arguments. | |
230 | // The return value of async_initiate is the result of our operation's | |
231 | // initiating function. | |
232 | // | |
233 | // Note that we wrap non-const reference arguments in std::reference_wrapper | |
234 | // to prevent incorrect decay-copies of these objects. | |
235 | return boost::asio::async_initiate< | |
236 | CompletionToken, void(boost::system::error_code)>( | |
237 | async_write_message_initiation(), token, std::ref(socket), | |
238 | std::move(encoded_message), repeat_count, std::move(delay_timer)); | |
239 | } | |
240 | ||
241 | //------------------------------------------------------------------------------ | |
242 | ||
243 | void test_callback() | |
244 | { | |
245 | boost::asio::io_context io_context; | |
246 | ||
247 | tcp::acceptor acceptor(io_context, {tcp::v4(), 55555}); | |
248 | tcp::socket socket = acceptor.accept(); | |
249 | ||
250 | // Test our asynchronous operation using a lambda as a callback. | |
251 | async_write_messages(socket, "Testing callback\r\n", 5, | |
252 | [](const boost::system::error_code& error) | |
253 | { | |
254 | if (!error) | |
255 | { | |
256 | std::cout << "Messages sent\n"; | |
257 | } | |
258 | else | |
259 | { | |
260 | std::cout << "Error: " << error.message() << "\n"; | |
261 | } | |
262 | }); | |
263 | ||
264 | io_context.run(); | |
265 | } | |
266 | ||
267 | //------------------------------------------------------------------------------ | |
268 | ||
269 | void test_future() | |
270 | { | |
271 | boost::asio::io_context io_context; | |
272 | ||
273 | tcp::acceptor acceptor(io_context, {tcp::v4(), 55555}); | |
274 | tcp::socket socket = acceptor.accept(); | |
275 | ||
276 | // Test our asynchronous operation using the use_future completion token. | |
277 | // This token causes the operation's initiating function to return a future, | |
278 | // which may be used to synchronously wait for the result of the operation. | |
279 | std::future<void> f = async_write_messages( | |
280 | socket, "Testing future\r\n", 5, boost::asio::use_future); | |
281 | ||
282 | io_context.run(); | |
283 | ||
284 | try | |
285 | { | |
286 | // Get the result of the operation. | |
287 | f.get(); | |
288 | std::cout << "Messages sent\n"; | |
289 | } | |
290 | catch (const std::exception& e) | |
291 | { | |
292 | std::cout << "Error: " << e.what() << "\n"; | |
293 | } | |
294 | } | |
295 | ||
296 | //------------------------------------------------------------------------------ | |
297 | ||
298 | int main() | |
299 | { | |
300 | test_callback(); | |
301 | test_future(); | |
302 | } |