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