]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // async_tcp_client.cpp | |
3 | // ~~~~~~~~~~~~~~~~~~~~ | |
4 | // | |
11fdf7f2 | 5 | // Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
7c673cae FG |
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 | ||
11fdf7f2 | 11 | #include <boost/asio/buffer.hpp> |
b32b8144 | 12 | #include <boost/asio/io_context.hpp> |
7c673cae FG |
13 | #include <boost/asio/ip/tcp.hpp> |
14 | #include <boost/asio/read_until.hpp> | |
11fdf7f2 | 15 | #include <boost/asio/steady_timer.hpp> |
7c673cae FG |
16 | #include <boost/asio/write.hpp> |
17 | #include <boost/bind.hpp> | |
18 | #include <iostream> | |
11fdf7f2 | 19 | #include <string> |
7c673cae | 20 | |
11fdf7f2 | 21 | using boost::asio::steady_timer; |
7c673cae FG |
22 | using boost::asio::ip::tcp; |
23 | ||
24 | // | |
25 | // This class manages socket timeouts by applying the concept of a deadline. | |
26 | // Some asynchronous operations are given deadlines by which they must complete. | |
27 | // Deadlines are enforced by an "actor" that persists for the lifetime of the | |
28 | // client object: | |
29 | // | |
30 | // +----------------+ | |
31 | // | | | |
32 | // | check_deadline |<---+ | |
33 | // | | | | |
34 | // +----------------+ | async_wait() | |
35 | // | | | |
36 | // +---------+ | |
37 | // | |
38 | // If the deadline actor determines that the deadline has expired, the socket | |
39 | // is closed and any outstanding operations are consequently cancelled. | |
40 | // | |
41 | // Connection establishment involves trying each endpoint in turn until a | |
42 | // connection is successful, or the available endpoints are exhausted. If the | |
43 | // deadline actor closes the socket, the connect actor is woken up and moves to | |
44 | // the next endpoint. | |
45 | // | |
46 | // +---------------+ | |
47 | // | | | |
48 | // | start_connect |<---+ | |
49 | // | | | | |
50 | // +---------------+ | | |
51 | // | | | |
52 | // async_- | +----------------+ | |
53 | // connect() | | | | |
54 | // +--->| handle_connect | | |
55 | // | | | |
56 | // +----------------+ | |
57 | // : | |
58 | // Once a connection is : | |
59 | // made, the connect : | |
60 | // actor forks in two - : | |
61 | // : | |
62 | // an actor for reading : and an actor for | |
63 | // inbound messages: : sending heartbeats: | |
64 | // : | |
65 | // +------------+ : +-------------+ | |
66 | // | |<- - - - -+- - - - ->| | | |
67 | // | start_read | | start_write |<---+ | |
68 | // | |<---+ | | | | |
69 | // +------------+ | +-------------+ | async_wait() | |
70 | // | | | | | |
71 | // async_- | +-------------+ async_- | +--------------+ | |
72 | // read_- | | | write() | | | | |
73 | // until() +--->| handle_read | +--->| handle_write | | |
74 | // | | | | | |
75 | // +-------------+ +--------------+ | |
76 | // | |
77 | // The input actor reads messages from the socket, where messages are delimited | |
78 | // by the newline character. The deadline for a complete message is 30 seconds. | |
79 | // | |
80 | // The heartbeat actor sends a heartbeat (a message that consists of a single | |
81 | // newline character) every 10 seconds. In this example, no deadline is applied | |
11fdf7f2 | 82 | // to message sending. |
7c673cae FG |
83 | // |
84 | class client | |
85 | { | |
86 | public: | |
b32b8144 | 87 | client(boost::asio::io_context& io_context) |
7c673cae | 88 | : stopped_(false), |
b32b8144 FG |
89 | socket_(io_context), |
90 | deadline_(io_context), | |
91 | heartbeat_timer_(io_context) | |
7c673cae FG |
92 | { |
93 | } | |
94 | ||
95 | // Called by the user of the client class to initiate the connection process. | |
b32b8144 FG |
96 | // The endpoints will have been obtained using a tcp::resolver. |
97 | void start(tcp::resolver::results_type endpoints) | |
7c673cae FG |
98 | { |
99 | // Start the connect actor. | |
b32b8144 FG |
100 | endpoints_ = endpoints; |
101 | start_connect(endpoints_.begin()); | |
7c673cae FG |
102 | |
103 | // Start the deadline actor. You will note that we're not setting any | |
104 | // particular deadline here. Instead, the connect and input actors will | |
105 | // update the deadline prior to each asynchronous operation. | |
106 | deadline_.async_wait(boost::bind(&client::check_deadline, this)); | |
107 | } | |
108 | ||
109 | // This function terminates all the actors to shut down the connection. It | |
110 | // may be called by the user of the client class, or by the class itself in | |
111 | // response to graceful termination or an unrecoverable error. | |
112 | void stop() | |
113 | { | |
114 | stopped_ = true; | |
115 | boost::system::error_code ignored_ec; | |
116 | socket_.close(ignored_ec); | |
117 | deadline_.cancel(); | |
118 | heartbeat_timer_.cancel(); | |
119 | } | |
120 | ||
121 | private: | |
b32b8144 | 122 | void start_connect(tcp::resolver::results_type::iterator endpoint_iter) |
7c673cae | 123 | { |
b32b8144 | 124 | if (endpoint_iter != endpoints_.end()) |
7c673cae FG |
125 | { |
126 | std::cout << "Trying " << endpoint_iter->endpoint() << "...\n"; | |
127 | ||
128 | // Set a deadline for the connect operation. | |
11fdf7f2 | 129 | deadline_.expires_after(boost::asio::chrono::seconds(60)); |
7c673cae FG |
130 | |
131 | // Start the asynchronous connect operation. | |
132 | socket_.async_connect(endpoint_iter->endpoint(), | |
133 | boost::bind(&client::handle_connect, | |
134 | this, _1, endpoint_iter)); | |
135 | } | |
136 | else | |
137 | { | |
138 | // There are no more endpoints to try. Shut down the client. | |
139 | stop(); | |
140 | } | |
141 | } | |
142 | ||
143 | void handle_connect(const boost::system::error_code& ec, | |
b32b8144 | 144 | tcp::resolver::results_type::iterator endpoint_iter) |
7c673cae FG |
145 | { |
146 | if (stopped_) | |
147 | return; | |
148 | ||
149 | // The async_connect() function automatically opens the socket at the start | |
150 | // of the asynchronous operation. If the socket is closed at this time then | |
151 | // the timeout handler must have run first. | |
152 | if (!socket_.is_open()) | |
153 | { | |
154 | std::cout << "Connect timed out\n"; | |
155 | ||
156 | // Try the next available endpoint. | |
157 | start_connect(++endpoint_iter); | |
158 | } | |
159 | ||
160 | // Check if the connect operation failed before the deadline expired. | |
161 | else if (ec) | |
162 | { | |
163 | std::cout << "Connect error: " << ec.message() << "\n"; | |
164 | ||
165 | // We need to close the socket used in the previous connection attempt | |
166 | // before starting a new one. | |
167 | socket_.close(); | |
168 | ||
169 | // Try the next available endpoint. | |
170 | start_connect(++endpoint_iter); | |
171 | } | |
172 | ||
173 | // Otherwise we have successfully established a connection. | |
174 | else | |
175 | { | |
176 | std::cout << "Connected to " << endpoint_iter->endpoint() << "\n"; | |
177 | ||
178 | // Start the input actor. | |
179 | start_read(); | |
180 | ||
181 | // Start the heartbeat actor. | |
182 | start_write(); | |
183 | } | |
184 | } | |
185 | ||
186 | void start_read() | |
187 | { | |
188 | // Set a deadline for the read operation. | |
11fdf7f2 | 189 | deadline_.expires_after(boost::asio::chrono::seconds(30)); |
7c673cae FG |
190 | |
191 | // Start an asynchronous operation to read a newline-delimited message. | |
11fdf7f2 TL |
192 | boost::asio::async_read_until(socket_, |
193 | boost::asio::dynamic_buffer(input_buffer_), '\n', | |
194 | boost::bind(&client::handle_read, this, _1, _2)); | |
7c673cae FG |
195 | } |
196 | ||
11fdf7f2 | 197 | void handle_read(const boost::system::error_code& ec, std::size_t n) |
7c673cae FG |
198 | { |
199 | if (stopped_) | |
200 | return; | |
201 | ||
202 | if (!ec) | |
203 | { | |
204 | // Extract the newline-delimited message from the buffer. | |
11fdf7f2 TL |
205 | std::string line(input_buffer_.substr(0, n - 1)); |
206 | input_buffer_.erase(0, n); | |
7c673cae FG |
207 | |
208 | // Empty messages are heartbeats and so ignored. | |
209 | if (!line.empty()) | |
210 | { | |
211 | std::cout << "Received: " << line << "\n"; | |
212 | } | |
213 | ||
214 | start_read(); | |
215 | } | |
216 | else | |
217 | { | |
218 | std::cout << "Error on receive: " << ec.message() << "\n"; | |
219 | ||
220 | stop(); | |
221 | } | |
222 | } | |
223 | ||
224 | void start_write() | |
225 | { | |
226 | if (stopped_) | |
227 | return; | |
228 | ||
229 | // Start an asynchronous operation to send a heartbeat message. | |
230 | boost::asio::async_write(socket_, boost::asio::buffer("\n", 1), | |
231 | boost::bind(&client::handle_write, this, _1)); | |
232 | } | |
233 | ||
234 | void handle_write(const boost::system::error_code& ec) | |
235 | { | |
236 | if (stopped_) | |
237 | return; | |
238 | ||
239 | if (!ec) | |
240 | { | |
241 | // Wait 10 seconds before sending the next heartbeat. | |
11fdf7f2 | 242 | heartbeat_timer_.expires_after(boost::asio::chrono::seconds(10)); |
7c673cae FG |
243 | heartbeat_timer_.async_wait(boost::bind(&client::start_write, this)); |
244 | } | |
245 | else | |
246 | { | |
247 | std::cout << "Error on heartbeat: " << ec.message() << "\n"; | |
248 | ||
249 | stop(); | |
250 | } | |
251 | } | |
252 | ||
253 | void check_deadline() | |
254 | { | |
255 | if (stopped_) | |
256 | return; | |
257 | ||
258 | // Check whether the deadline has passed. We compare the deadline against | |
259 | // the current time since a new asynchronous operation may have moved the | |
260 | // deadline before this actor had a chance to run. | |
11fdf7f2 | 261 | if (deadline_.expiry() <= steady_timer::clock_type::now()) |
7c673cae FG |
262 | { |
263 | // The deadline has passed. The socket is closed so that any outstanding | |
264 | // asynchronous operations are cancelled. | |
265 | socket_.close(); | |
266 | ||
11fdf7f2 TL |
267 | // There is no longer an active deadline. The expiry is set to the |
268 | // maximum time point so that the actor takes no action until a new | |
269 | // deadline is set. | |
270 | deadline_.expires_at(steady_timer::time_point::max()); | |
7c673cae FG |
271 | } |
272 | ||
273 | // Put the actor back to sleep. | |
274 | deadline_.async_wait(boost::bind(&client::check_deadline, this)); | |
275 | } | |
276 | ||
277 | private: | |
278 | bool stopped_; | |
b32b8144 | 279 | tcp::resolver::results_type endpoints_; |
7c673cae | 280 | tcp::socket socket_; |
11fdf7f2 TL |
281 | std::string input_buffer_; |
282 | steady_timer deadline_; | |
283 | steady_timer heartbeat_timer_; | |
7c673cae FG |
284 | }; |
285 | ||
286 | int main(int argc, char* argv[]) | |
287 | { | |
288 | try | |
289 | { | |
290 | if (argc != 3) | |
291 | { | |
292 | std::cerr << "Usage: client <host> <port>\n"; | |
293 | return 1; | |
294 | } | |
295 | ||
b32b8144 FG |
296 | boost::asio::io_context io_context; |
297 | tcp::resolver r(io_context); | |
298 | client c(io_context); | |
7c673cae | 299 | |
b32b8144 | 300 | c.start(r.resolve(argv[1], argv[2])); |
7c673cae | 301 | |
b32b8144 | 302 | io_context.run(); |
7c673cae FG |
303 | } |
304 | catch (std::exception& e) | |
305 | { | |
306 | std::cerr << "Exception: " << e.what() << "\n"; | |
307 | } | |
308 | ||
309 | return 0; | |
310 | } |