]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // |
2 | // composed_8.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/compose.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_compose 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 | // using asio's stackless coroutines support to express the flow of control. It | |
35 | // automatically serialises a message, using its I/O streams insertion | |
36 | // operator, before sending it N times on the socket. To do this, it must | |
37 | // allocate a buffer for the encoded message and ensure this buffer's validity | |
38 | // until all underlying async_write operation complete. A one second delay is | |
39 | // inserted prior to each write operation, using a steady_timer. | |
40 | ||
41 | #include <boost/asio/yield.hpp> | |
42 | ||
43 | // In this example, the composed operation's logic is implemented as a state | |
44 | // machine within a hand-crafted function object. | |
45 | struct async_write_messages_implementation | |
46 | { | |
47 | // The implementation holds a reference to the socket as it is used for | |
48 | // multiple async_write operations. | |
49 | tcp::socket& socket_; | |
50 | ||
51 | // The allocated buffer for the encoded message. The std::unique_ptr smart | |
52 | // pointer is move-only, and as a consequence our implementation is also | |
53 | // move-only. | |
54 | std::unique_ptr<std::string> encoded_message_; | |
55 | ||
56 | // The repeat count remaining. | |
57 | std::size_t repeat_count_; | |
58 | ||
59 | // A steady timer used for introducing a delay. | |
60 | std::unique_ptr<boost::asio::steady_timer> delay_timer_; | |
61 | ||
62 | // The coroutine state. | |
63 | boost::asio::coroutine coro_; | |
64 | ||
65 | // The first argument to our function object's call operator is a reference | |
66 | // to the enclosing intermediate completion handler. This intermediate | |
67 | // completion handler is provided for us by the boost::asio::async_compose | |
68 | // function, and takes care of all the details required to implement a | |
69 | // conforming asynchronous operation. When calling an underlying asynchronous | |
70 | // operation, we pass it this enclosing intermediate completion handler | |
71 | // as the completion token. | |
72 | // | |
73 | // All arguments after the first must be defaulted to allow the state machine | |
74 | // to be started, as well as to allow the completion handler to match the | |
75 | // completion signature of both the async_write and steady_timer::async_wait | |
76 | // operations. | |
77 | template <typename Self> | |
78 | void operator()(Self& self, | |
79 | const boost::system::error_code& error = boost::system::error_code(), | |
80 | std::size_t = 0) | |
81 | { | |
82 | reenter (coro_) | |
83 | { | |
84 | while (repeat_count_ > 0) | |
85 | { | |
86 | --repeat_count_; | |
87 | ||
88 | delay_timer_->expires_after(std::chrono::seconds(1)); | |
89 | yield delay_timer_->async_wait(std::move(self)); | |
90 | if (error) | |
91 | break; | |
92 | ||
93 | yield boost::asio::async_write(socket_, | |
94 | boost::asio::buffer(*encoded_message_), std::move(self)); | |
95 | if (error) | |
96 | break; | |
97 | } | |
98 | ||
99 | // Deallocate the encoded message and delay timer before calling the | |
100 | // user-supplied completion handler. | |
101 | encoded_message_.reset(); | |
102 | delay_timer_.reset(); | |
103 | ||
104 | // Call the user-supplied handler with the result of the operation. | |
105 | self.complete(error); | |
106 | } | |
107 | } | |
108 | }; | |
109 | ||
110 | #include <boost/asio/unyield.hpp> | |
111 | ||
112 | template <typename T, typename CompletionToken> | |
113 | auto async_write_messages(tcp::socket& socket, | |
114 | const T& message, std::size_t repeat_count, | |
115 | CompletionToken&& token) | |
116 | // The return type of the initiating function is deduced from the combination | |
117 | // of CompletionToken type and the completion handler's signature. When the | |
118 | // completion token is a simple callback, the return type is always void. | |
119 | // In this example, when the completion token is boost::asio::yield_context | |
120 | // (used for stackful coroutines) the return type would be also be void, as | |
121 | // there is no non-error argument to the completion handler. When the | |
122 | // completion token is boost::asio::use_future it would be std::future<void>. | |
123 | -> typename boost::asio::async_result< | |
124 | typename std::decay<CompletionToken>::type, | |
125 | void(boost::system::error_code)>::return_type | |
126 | { | |
127 | // Encode the message and copy it into an allocated buffer. The buffer will | |
128 | // be maintained for the lifetime of the composed asynchronous operation. | |
129 | std::ostringstream os; | |
130 | os << message; | |
131 | std::unique_ptr<std::string> encoded_message(new std::string(os.str())); | |
132 | ||
133 | // Create a steady_timer to be used for the delay between messages. | |
134 | std::unique_ptr<boost::asio::steady_timer> delay_timer( | |
135 | new boost::asio::steady_timer(socket.get_executor())); | |
136 | ||
137 | // The boost::asio::async_compose function takes: | |
138 | // | |
139 | // - our asynchronous operation implementation, | |
140 | // - the completion token, | |
141 | // - the completion handler signature, and | |
142 | // - any I/O objects (or executors) used by the operation | |
143 | // | |
144 | // It then wraps our implementation in an intermediate completion handler | |
145 | // that meets the requirements of a conforming asynchronous operation. This | |
146 | // includes tracking outstanding work against the I/O executors associated | |
147 | // with the operation (in this example, this is the socket's executor). | |
148 | return boost::asio::async_compose< | |
149 | CompletionToken, void(boost::system::error_code)>( | |
150 | async_write_messages_implementation{socket, | |
151 | std::move(encoded_message), repeat_count, | |
152 | std::move(delay_timer), boost::asio::coroutine()}, | |
153 | token, socket); | |
154 | } | |
155 | ||
156 | //------------------------------------------------------------------------------ | |
157 | ||
158 | void test_callback() | |
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 a lambda as a callback. | |
166 | async_write_messages(socket, "Testing callback\r\n", 5, | |
167 | [](const boost::system::error_code& error) | |
168 | { | |
169 | if (!error) | |
170 | { | |
171 | std::cout << "Messages sent\n"; | |
172 | } | |
173 | else | |
174 | { | |
175 | std::cout << "Error: " << error.message() << "\n"; | |
176 | } | |
177 | }); | |
178 | ||
179 | io_context.run(); | |
180 | } | |
181 | ||
182 | //------------------------------------------------------------------------------ | |
183 | ||
184 | void test_future() | |
185 | { | |
186 | boost::asio::io_context io_context; | |
187 | ||
188 | tcp::acceptor acceptor(io_context, {tcp::v4(), 55555}); | |
189 | tcp::socket socket = acceptor.accept(); | |
190 | ||
191 | // Test our asynchronous operation using the use_future completion token. | |
192 | // This token causes the operation's initiating function to return a future, | |
193 | // which may be used to synchronously wait for the result of the operation. | |
194 | std::future<void> f = async_write_messages( | |
195 | socket, "Testing future\r\n", 5, boost::asio::use_future); | |
196 | ||
197 | io_context.run(); | |
198 | ||
199 | try | |
200 | { | |
201 | // Get the result of the operation. | |
202 | f.get(); | |
203 | std::cout << "Messages sent\n"; | |
204 | } | |
205 | catch (const std::exception& e) | |
206 | { | |
207 | std::cout << "Error: " << e.what() << "\n"; | |
208 | } | |
209 | } | |
210 | ||
211 | //------------------------------------------------------------------------------ | |
212 | ||
213 | int main() | |
214 | { | |
215 | test_callback(); | |
216 | test_future(); | |
217 | } |