1 //
2 // composed_3.cpp
3 // ~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2021 Christopher M. Kohlhoff (chris at kohlhoff dot com)
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/bind_executor.hpp>
12 #include <boost/asio/io_context.hpp>
13 #include <boost/asio/ip/tcp.hpp>
14 #include <boost/asio/use_future.hpp>
15 #include <boost/asio/write.hpp>
16 #include <cstring>
17 #include <functional>
18 #include <iostream>
19 #include <string>
20 #include <type_traits>
21 #include <utility>
22
23 using boost::asio::ip::tcp;
24
25 // NOTE: This example requires the new boost::asio::async_initiate function. For
26 // an example that works with the Networking TS style of completion tokens,
27 // please see an older version of asio.
28
29 //------------------------------------------------------------------------------
30
31 // In this composed operation we repackage an existing operation, but with a
32 // different completion handler signature. The asynchronous operation
33 // requirements are met by delegating responsibility to the underlying
34 // operation.
35
36 // In addition to determining the mechanism by which an asynchronous operation
37 // delivers its result, a completion token also determines the time when the
38 // operation commences. For example, when the completion token is a simple
39 // callback the operation commences before the initiating function returns.
40 // However, if the completion token's delivery mechanism uses a future, we
41 // might instead want to defer initiation of the operation until the returned
42 // future object is waited upon.
43 //
44 // To enable this, when implementing an asynchronous operation we must package
45 // the initiation step as a function object.
46 struct async_write_message_initiation
47 {
48 // The initiation function object's call operator is passed the concrete
49 // completion handler produced by the completion token. This completion
50 // handler matches the asynchronous operation's completion handler signature,
51 // which in this example is:
52 //
53 // void(boost::system::error_code error)
54 //
55 // The initiation function object also receives any additional arguments
56 // required to start the operation. (Note: We could have instead passed these
57 // arguments as members in the initiaton function object. However, we should
58 // prefer to propagate them as function call arguments as this allows the
59 // completion token to optimise how they are passed. For example, a lazy
60 // future which defers initiation would need to make a decay-copy of the
61 // arguments, but when using a simple callback the arguments can be trivially
62 // forwarded straight through.)
63 template <typename CompletionHandler>
operator ()async_write_message_initiation64 void operator()(CompletionHandler&& completion_handler,
65 tcp::socket& socket, const char* message) const
66 {
67 // The async_write operation has a completion handler signature of:
68 //
69 // void(boost::system::error_code error, std::size n)
70 //
71 // This differs from our operation's signature in that it is also passed
72 // the number of bytes transferred as an argument of type std::size_t. We
73 // will adapt our completion handler to async_write's completion handler
74 // signature by using std::bind, which drops the additional argument.
75 //
76 // However, it is essential to the correctness of our composed operation
77 // that we preserve the executor of the user-supplied completion handler.
78 // The std::bind function will not do this for us, so we must do this by
79 // first obtaining the completion handler's associated executor (defaulting
80 // to the I/O executor - in this case the executor of the socket - if the
81 // completion handler does not have its own) ...
82 auto executor = boost::asio::get_associated_executor(
83 completion_handler, socket.get_executor());
84
85 // ... and then binding this executor to our adapted completion handler
86 // using the boost::asio::bind_executor function.
87 boost::asio::async_write(socket,
88 boost::asio::buffer(message, std::strlen(message)),
89 boost::asio::bind_executor(executor,
90 std::bind(std::forward<CompletionHandler>(
91 completion_handler), std::placeholders::_1)));
92 }
93 };
94
95 template <typename CompletionToken>
async_write_message(tcp::socket & socket,const char * message,CompletionToken && token)96 auto async_write_message(tcp::socket& socket,
97 const char* message, CompletionToken&& token)
98 // The return type of the initiating function is deduced from the combination
99 // of CompletionToken type and the completion handler's signature. When the
100 // completion token is a simple callback, the return type is always void.
101 // In this example, when the completion token is boost::asio::yield_context
102 // (used for stackful coroutines) the return type would be also be void, as
103 // there is no non-error argument to the completion handler. When the
104 // completion token is boost::asio::use_future it would be std::future<void>.
105 -> typename boost::asio::async_result<
106 typename std::decay<CompletionToken>::type,
107 void(boost::system::error_code)>::return_type
108 {
109 // The boost::asio::async_initiate function takes:
110 //
111 // - our initiation function object,
112 // - the completion token,
113 // - the completion handler signature, and
114 // - any additional arguments we need to initiate the operation.
115 //
116 // It then asks the completion token to create a completion handler (i.e. a
117 // callback) with the specified signature, and invoke the initiation function
118 // object with this completion handler as well as the additional arguments.
119 // The return value of async_initiate is the result of our operation's
120 // initiating function.
121 //
122 // Note that we wrap non-const reference arguments in std::reference_wrapper
123 // to prevent incorrect decay-copies of these objects.
124 return boost::asio::async_initiate<
125 CompletionToken, void(boost::system::error_code)>(
126 async_write_message_initiation(),
127 token, std::ref(socket), message);
128 }
129
130 //------------------------------------------------------------------------------
131
test_callback()132 void test_callback()
133 {
134 boost::asio::io_context io_context;
135
136 tcp::acceptor acceptor(io_context, {tcp::v4(), 55555});
137 tcp::socket socket = acceptor.accept();
138
139 // Test our asynchronous operation using a lambda as a callback.
140 async_write_message(socket, "Testing callback\r\n",
141 [](const boost::system::error_code& error)
142 {
143 if (!error)
144 {
145 std::cout << "Message sent\n";
146 }
147 else
148 {
149 std::cout << "Error: " << error.message() << "\n";
150 }
151 });
152
153 io_context.run();
154 }
155
156 //------------------------------------------------------------------------------
157
test_future()158 void test_future()
159 {
160 boost::asio::io_context io_context;
161
162 tcp::acceptor acceptor(io_context, {tcp::v4(), 55555});
163 tcp::socket socket = acceptor.accept();
164
165 // Test our asynchronous operation using the use_future completion token.
166 // This token causes the operation's initiating function to return a future,
167 // which may be used to synchronously wait for the result of the operation.
168 std::future<void> f = async_write_message(
169 socket, "Testing future\r\n", boost::asio::use_future);
170
171 io_context.run();
172
173 // Get the result of the operation.
174 try
175 {
176 // Get the result of the operation.
177 f.get();
178 std::cout << "Message sent\n";
179 }
180 catch (const std::exception& e)
181 {
182 std::cout << "Error: " << e.what() << "\n";
183 }
184 }
185
186 //------------------------------------------------------------------------------
187
main()188 int main()
189 {
190 test_callback();
191 test_future();
192 }
193