5 // Copyright (c) 2003-2022 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 boost::asio::executor_work_guard
<tcp::socket::executor_type
> io_work_
;
113 // The user-supplied completion handler, called once only on completion
114 // of the entire composed operation.
115 typename
std::decay
<decltype(completion_handler
)>::type handler_
;
117 // By having a default value for the second argument, this function call
118 // operator matches the completion signature of both the async_write and
119 // steady_timer::async_wait operations.
120 void operator()(const boost::system::error_code
& error
, std::size_t = 0)
128 if (repeat_count_
> 0)
132 delay_timer_
->expires_after(std::chrono::seconds(1));
133 delay_timer_
->async_wait(std::move(*this));
134 return; // Composed operation not yet complete.
136 break; // Composed operation complete, continue below.
139 boost::asio::async_write(socket_
,
140 boost::asio::buffer(*encoded_message_
), std::move(*this));
141 return; // Composed operation not yet complete.
145 // This point is reached only on completion of the entire composed
148 // We no longer have any future work coming for the I/O executor.
151 // Deallocate the encoded message before calling the user-supplied
152 // completion handler.
153 encoded_message_
.reset();
155 // Call the user-supplied handler with the result of the operation.
159 // It is essential to the correctness of our composed operation that we
160 // preserve the executor of the user-supplied completion handler. With a
161 // hand-crafted function object we can do this by defining a nested type
162 // executor_type and member function get_executor. These obtain the
163 // completion handler's associated executor, and default to the I/O
164 // executor - in this case the executor of the socket - if the completion
165 // handler does not have its own.
166 using executor_type
= boost::asio::associated_executor_t
<
167 typename
std::decay
<decltype(completion_handler
)>::type
,
168 tcp::socket::executor_type
>;
170 executor_type
get_executor() const noexcept
172 return boost::asio::get_associated_executor(
173 handler_
, socket_
.get_executor());
176 // Although not necessary for correctness, we may also preserve the
177 // allocator of the user-supplied completion handler. This is achieved by
178 // defining a nested type allocator_type and member function
179 // get_allocator. These obtain the completion handler's associated
180 // allocator, and default to std::allocator<void> if the completion
181 // handler does not have its own.
182 using allocator_type
= boost::asio::associated_allocator_t
<
183 typename
std::decay
<decltype(completion_handler
)>::type
,
184 std::allocator
<void>>;
186 allocator_type
get_allocator() const noexcept
188 return boost::asio::get_associated_allocator(
189 handler_
, std::allocator
<void>{});
193 // Initiate the underlying async_write operation using our intermediate
194 // completion handler.
195 auto encoded_message_buffer
= boost::asio::buffer(*encoded_message
);
196 boost::asio::async_write(socket
, encoded_message_buffer
,
197 intermediate_completion_handler
{
198 socket
, std::move(encoded_message
),
199 repeat_count
, std::move(delay_timer
),
200 intermediate_completion_handler::starting
,
201 boost::asio::make_work_guard(socket
.get_executor()),
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 //------------------------------------------------------------------------------