2 // async_tcp_client.cpp
3 // ~~~~~~~~~~~~~~~~~~~~
5 // Copyright (c) 2003-2017 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/deadline_timer.hpp>
12 #include <boost/asio/io_context.hpp>
13 #include <boost/asio/ip/tcp.hpp>
14 #include <boost/asio/read_until.hpp>
15 #include <boost/asio/streambuf.hpp>
16 #include <boost/asio/write.hpp>
17 #include <boost/bind.hpp>
20 using boost::asio::deadline_timer
;
21 using boost::asio::ip::tcp
;
24 // This class manages socket timeouts by applying the concept of a deadline.
25 // Some asynchronous operations are given deadlines by which they must complete.
26 // Deadlines are enforced by an "actor" that persists for the lifetime of the
31 // | check_deadline |<---+
33 // +----------------+ | async_wait()
37 // If the deadline actor determines that the deadline has expired, the socket
38 // is closed and any outstanding operations are consequently cancelled.
40 // Connection establishment involves trying each endpoint in turn until a
41 // connection is successful, or the available endpoints are exhausted. If the
42 // deadline actor closes the socket, the connect actor is woken up and moves to
47 // | start_connect |<---+
49 // +---------------+ |
51 // async_- | +----------------+
53 // +--->| handle_connect |
57 // Once a connection is :
58 // made, the connect :
59 // actor forks in two - :
61 // an actor for reading : and an actor for
62 // inbound messages: : sending heartbeats:
64 // +------------+ : +-------------+
65 // | |<- - - - -+- - - - ->| |
66 // | start_read | | start_write |<---+
68 // +------------+ | +-------------+ | async_wait()
70 // async_- | +-------------+ async_- | +--------------+
71 // read_- | | | write() | | |
72 // until() +--->| handle_read | +--->| handle_write |
74 // +-------------+ +--------------+
76 // The input actor reads messages from the socket, where messages are delimited
77 // by the newline character. The deadline for a complete message is 30 seconds.
79 // The heartbeat actor sends a heartbeat (a message that consists of a single
80 // newline character) every 10 seconds. In this example, no deadline is applied
86 client(boost::asio::io_context
& io_context
)
89 deadline_(io_context
),
90 heartbeat_timer_(io_context
)
94 // Called by the user of the client class to initiate the connection process.
95 // The endpoints will have been obtained using a tcp::resolver.
96 void start(tcp::resolver::results_type endpoints
)
98 // Start the connect actor.
99 endpoints_
= endpoints
;
100 start_connect(endpoints_
.begin());
102 // Start the deadline actor. You will note that we're not setting any
103 // particular deadline here. Instead, the connect and input actors will
104 // update the deadline prior to each asynchronous operation.
105 deadline_
.async_wait(boost::bind(&client::check_deadline
, this));
108 // This function terminates all the actors to shut down the connection. It
109 // may be called by the user of the client class, or by the class itself in
110 // response to graceful termination or an unrecoverable error.
114 boost::system::error_code ignored_ec
;
115 socket_
.close(ignored_ec
);
117 heartbeat_timer_
.cancel();
121 void start_connect(tcp::resolver::results_type::iterator endpoint_iter
)
123 if (endpoint_iter
!= endpoints_
.end())
125 std::cout
<< "Trying " << endpoint_iter
->endpoint() << "...\n";
127 // Set a deadline for the connect operation.
128 deadline_
.expires_from_now(boost::posix_time::seconds(60));
130 // Start the asynchronous connect operation.
131 socket_
.async_connect(endpoint_iter
->endpoint(),
132 boost::bind(&client::handle_connect
,
133 this, _1
, endpoint_iter
));
137 // There are no more endpoints to try. Shut down the client.
142 void handle_connect(const boost::system::error_code
& ec
,
143 tcp::resolver::results_type::iterator endpoint_iter
)
148 // The async_connect() function automatically opens the socket at the start
149 // of the asynchronous operation. If the socket is closed at this time then
150 // the timeout handler must have run first.
151 if (!socket_
.is_open())
153 std::cout
<< "Connect timed out\n";
155 // Try the next available endpoint.
156 start_connect(++endpoint_iter
);
159 // Check if the connect operation failed before the deadline expired.
162 std::cout
<< "Connect error: " << ec
.message() << "\n";
164 // We need to close the socket used in the previous connection attempt
165 // before starting a new one.
168 // Try the next available endpoint.
169 start_connect(++endpoint_iter
);
172 // Otherwise we have successfully established a connection.
175 std::cout
<< "Connected to " << endpoint_iter
->endpoint() << "\n";
177 // Start the input actor.
180 // Start the heartbeat actor.
187 // Set a deadline for the read operation.
188 deadline_
.expires_from_now(boost::posix_time::seconds(30));
190 // Start an asynchronous operation to read a newline-delimited message.
191 boost::asio::async_read_until(socket_
, input_buffer_
, '\n',
192 boost::bind(&client::handle_read
, this, _1
));
195 void handle_read(const boost::system::error_code
& ec
)
202 // Extract the newline-delimited message from the buffer.
204 std::istream
is(&input_buffer_
);
205 std::getline(is
, line
);
207 // Empty messages are heartbeats and so ignored.
210 std::cout
<< "Received: " << line
<< "\n";
217 std::cout
<< "Error on receive: " << ec
.message() << "\n";
228 // Start an asynchronous operation to send a heartbeat message.
229 boost::asio::async_write(socket_
, boost::asio::buffer("\n", 1),
230 boost::bind(&client::handle_write
, this, _1
));
233 void handle_write(const boost::system::error_code
& ec
)
240 // Wait 10 seconds before sending the next heartbeat.
241 heartbeat_timer_
.expires_from_now(boost::posix_time::seconds(10));
242 heartbeat_timer_
.async_wait(boost::bind(&client::start_write
, this));
246 std::cout
<< "Error on heartbeat: " << ec
.message() << "\n";
252 void check_deadline()
257 // Check whether the deadline has passed. We compare the deadline against
258 // the current time since a new asynchronous operation may have moved the
259 // deadline before this actor had a chance to run.
260 if (deadline_
.expires_at() <= deadline_timer::traits_type::now())
262 // The deadline has passed. The socket is closed so that any outstanding
263 // asynchronous operations are cancelled.
266 // There is no longer an active deadline. The expiry is set to positive
267 // infinity so that the actor takes no action until a new deadline is set.
268 deadline_
.expires_at(boost::posix_time::pos_infin
);
271 // Put the actor back to sleep.
272 deadline_
.async_wait(boost::bind(&client::check_deadline
, this));
277 tcp::resolver::results_type endpoints_
;
279 boost::asio::streambuf input_buffer_
;
280 deadline_timer deadline_
;
281 deadline_timer heartbeat_timer_
;
284 int main(int argc
, char* argv
[])
290 std::cerr
<< "Usage: client <host> <port>\n";
294 boost::asio::io_context io_context
;
295 tcp::resolver
r(io_context
);
296 client
c(io_context
);
298 c
.start(r
.resolve(argv
[1], argv
[2]));
302 catch (std::exception
& e
)
304 std::cerr
<< "Exception: " << e
.what() << "\n";