5 // Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
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)
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>
22 #include <type_traits>
25 using boost::asio::ip::tcp
;
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.
31 //------------------------------------------------------------------------------
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.
40 template <typename T
, typename CompletionToken
>
41 auto async_write_messages(tcp::socket
& socket
,
42 const T
& message
, std::size_t repeat_count
,
43 CompletionToken
&& token
)
44 // The return type of the initiating function is deduced from the combination
45 // of CompletionToken type and the completion handler's signature. When the
46 // completion token is a simple callback, the return type is always void.
47 // In this example, when the completion token is boost::asio::yield_context
48 // (used for stackful coroutines) the return type would be also be void, as
49 // there is no non-error argument to the completion handler. When the
50 // completion token is boost::asio::use_future it would be std::future<void>.
52 // In C++14 we can omit the return type as it is automatically deduced from
53 // the return type of boost::asio::async_initiate.
55 // In addition to determining the mechanism by which an asynchronous
56 // operation delivers its result, a completion token also determines the time
57 // when the operation commences. For example, when the completion token is a
58 // simple callback the operation commences before the initiating function
59 // returns. However, if the completion token's delivery mechanism uses a
60 // future, we might instead want to defer initiation of the operation until
61 // the returned future object is waited upon.
63 // To enable this, when implementing an asynchronous operation we must
64 // package the initiation step as a function object. The initiation function
65 // object's call operator is passed the concrete completion handler produced
66 // by the completion token. This completion handler matches the asynchronous
67 // operation's completion handler signature, which in this example is:
69 // void(boost::system::error_code error)
71 // The initiation function object also receives any additional arguments
72 // required to start the operation. (Note: We could have instead passed these
73 // arguments in the lambda capture set. However, we should prefer to
74 // propagate them as function call arguments as this allows the completion
75 // token to optimise how they are passed. For example, a lazy future which
76 // defers initiation would need to make a decay-copy of the arguments, but
77 // when using a simple callback the arguments can be trivially forwarded
79 auto initiation
= [](auto&& completion_handler
, tcp::socket
& socket
,
80 std::unique_ptr
<std::string
> encoded_message
, std::size_t repeat_count
,
81 std::unique_ptr
<boost::asio::steady_timer
> delay_timer
)
83 // In this example, the composed operation's intermediate completion
84 // handler is implemented as a hand-crafted function object.
85 struct intermediate_completion_handler
87 // The intermediate completion handler holds a reference to the socket as
88 // it is used for multiple async_write operations, as well as for
89 // obtaining the I/O executor (see get_executor below).
92 // The allocated buffer for the encoded message. The std::unique_ptr
93 // smart pointer is move-only, and as a consequence our intermediate
94 // completion handler is also move-only.
95 std::unique_ptr
<std::string
> encoded_message_
;
97 // The repeat count remaining.
98 std::size_t repeat_count_
;
100 // A steady timer used for introducing a delay.
101 std::unique_ptr
<boost::asio::steady_timer
> delay_timer_
;
103 // To manage the cycle between the multiple underlying asychronous
104 // operations, our intermediate completion handler is implemented as a
106 enum { starting
, waiting
, writing
} state_
;
108 // As our composed operation performs multiple underlying I/O operations,
109 // we should maintain a work object against the I/O executor. This tells
110 // the I/O executor that there is still more work to come in the future.
111 typename
std::decay
<decltype(boost::asio::prefer(
112 std::declval
<tcp::socket::executor_type
>(),
113 boost::asio::execution::outstanding_work
.tracked
))>::type io_work_
;
115 // The user-supplied completion handler, called once only on completion
116 // of the entire composed operation.
117 typename
std::decay
<decltype(completion_handler
)>::type handler_
;
119 // By having a default value for the second argument, this function call
120 // operator matches the completion signature of both the async_write and
121 // steady_timer::async_wait operations.
122 void operator()(const boost::system::error_code
& error
, std::size_t = 0)
130 if (repeat_count_
> 0)
134 delay_timer_
->expires_after(std::chrono::seconds(1));
135 delay_timer_
->async_wait(std::move(*this));
136 return; // Composed operation not yet complete.
138 break; // Composed operation complete, continue below.
141 boost::asio::async_write(socket_
,
142 boost::asio::buffer(*encoded_message_
), std::move(*this));
143 return; // Composed operation not yet complete.
147 // This point is reached only on completion of the entire composed
150 // Deallocate the encoded message before calling the user-supplied
151 // completion handler.
152 encoded_message_
.reset();
154 // Call the user-supplied handler with the result of the operation.
158 // It is essential to the correctness of our composed operation that we
159 // preserve the executor of the user-supplied completion handler. With a
160 // hand-crafted function object we can do this by defining a nested type
161 // executor_type and member function get_executor. These obtain the
162 // completion handler's associated executor, and default to the I/O
163 // executor - in this case the executor of the socket - if the completion
164 // handler does not have its own.
165 using executor_type
= boost::asio::associated_executor_t
<
166 typename
std::decay
<decltype(completion_handler
)>::type
,
167 tcp::socket::executor_type
>;
169 executor_type
get_executor() const noexcept
171 return boost::asio::get_associated_executor(
172 handler_
, socket_
.get_executor());
175 // Although not necessary for correctness, we may also preserve the
176 // allocator of the user-supplied completion handler. This is achieved by
177 // defining a nested type allocator_type and member function
178 // get_allocator. These obtain the completion handler's associated
179 // allocator, and default to std::allocator<void> if the completion
180 // handler does not have its own.
181 using allocator_type
= boost::asio::associated_allocator_t
<
182 typename
std::decay
<decltype(completion_handler
)>::type
,
183 std::allocator
<void>>;
185 allocator_type
get_allocator() const noexcept
187 return boost::asio::get_associated_allocator(
188 handler_
, std::allocator
<void>{});
192 // Initiate the underlying async_write operation using our intermediate
193 // completion handler.
194 auto encoded_message_buffer
= boost::asio::buffer(*encoded_message
);
195 boost::asio::async_write(socket
, encoded_message_buffer
,
196 intermediate_completion_handler
{
197 socket
, std::move(encoded_message
),
198 repeat_count
, std::move(delay_timer
),
199 intermediate_completion_handler::starting
,
200 boost::asio::prefer(socket
.get_executor(),
201 boost::asio::execution::outstanding_work
.tracked
),
202 std::forward
<decltype(completion_handler
)>(completion_handler
)});
205 // Encode the message and copy it into an allocated buffer. The buffer will
206 // be maintained for the lifetime of the composed asynchronous operation.
207 std::ostringstream os
;
209 std::unique_ptr
<std::string
> encoded_message(new std::string(os
.str()));
211 // Create a steady_timer to be used for the delay between messages.
212 std::unique_ptr
<boost::asio::steady_timer
> delay_timer(
213 new boost::asio::steady_timer(socket
.get_executor()));
215 // The boost::asio::async_initiate function takes:
217 // - our initiation function object,
218 // - the completion token,
219 // - the completion handler signature, and
220 // - any additional arguments we need to initiate the operation.
222 // It then asks the completion token to create a completion handler (i.e. a
223 // callback) with the specified signature, and invoke the initiation function
224 // object with this completion handler as well as the additional arguments.
225 // The return value of async_initiate is the result of our operation's
226 // initiating function.
228 // Note that we wrap non-const reference arguments in std::reference_wrapper
229 // to prevent incorrect decay-copies of these objects.
230 return boost::asio::async_initiate
<
231 CompletionToken
, void(boost::system::error_code
)>(
232 initiation
, token
, std::ref(socket
),
233 std::move(encoded_message
), repeat_count
,
234 std::move(delay_timer
));
237 //------------------------------------------------------------------------------
241 boost::asio::io_context io_context
;
243 tcp::acceptor
acceptor(io_context
, {tcp::v4(), 55555});
244 tcp::socket socket
= acceptor
.accept();
246 // Test our asynchronous operation using a lambda as a callback.
247 async_write_messages(socket
, "Testing callback\r\n", 5,
248 [](const boost::system::error_code
& error
)
252 std::cout
<< "Messages sent\n";
256 std::cout
<< "Error: " << error
.message() << "\n";
263 //------------------------------------------------------------------------------
267 boost::asio::io_context io_context
;
269 tcp::acceptor
acceptor(io_context
, {tcp::v4(), 55555});
270 tcp::socket socket
= acceptor
.accept();
272 // Test our asynchronous operation using the use_future completion token.
273 // This token causes the operation's initiating function to return a future,
274 // which may be used to synchronously wait for the result of the operation.
275 std::future
<void> f
= async_write_messages(
276 socket
, "Testing future\r\n", 5, boost::asio::use_future
);
282 // Get the result of the operation.
284 std::cout
<< "Messages sent\n";
286 catch (const std::exception
& e
)
288 std::cout
<< "Error: " << e
.what() << "\n";
292 //------------------------------------------------------------------------------