2 // async_tcp_client.cpp
3 // ~~~~~~~~~~~~~~~~~~~~
5 // Copyright (c) 2003-2019 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/buffer.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/steady_timer.hpp>
16 #include <boost/asio/write.hpp>
17 #include <boost/bind.hpp>
21 using boost::asio::steady_timer
;
22 using boost::asio::ip::tcp
;
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
32 // | check_deadline |<---+
34 // +----------------+ | async_wait()
38 // If the deadline actor determines that the deadline has expired, the socket
39 // is closed and any outstanding operations are consequently cancelled.
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
48 // | start_connect |<---+
50 // +---------------+ |
52 // async_- | +----------------+
54 // +--->| handle_connect |
58 // Once a connection is :
59 // made, the connect :
60 // actor forks in two - :
62 // an actor for reading : and an actor for
63 // inbound messages: : sending heartbeats:
65 // +------------+ : +-------------+
66 // | |<- - - - -+- - - - ->| |
67 // | start_read | | start_write |<---+
69 // +------------+ | +-------------+ | async_wait()
71 // async_- | +-------------+ async_- | +--------------+
72 // read_- | | | write() | | |
73 // until() +--->| handle_read | +--->| handle_write |
75 // +-------------+ +--------------+
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.
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
82 // to message sending.
87 client(boost::asio::io_context
& io_context
)
90 deadline_(io_context
),
91 heartbeat_timer_(io_context
)
95 // Called by the user of the client class to initiate the connection process.
96 // The endpoints will have been obtained using a tcp::resolver.
97 void start(tcp::resolver::results_type endpoints
)
99 // Start the connect actor.
100 endpoints_
= endpoints
;
101 start_connect(endpoints_
.begin());
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));
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.
115 boost::system::error_code ignored_ec
;
116 socket_
.close(ignored_ec
);
118 heartbeat_timer_
.cancel();
122 void start_connect(tcp::resolver::results_type::iterator endpoint_iter
)
124 if (endpoint_iter
!= endpoints_
.end())
126 std::cout
<< "Trying " << endpoint_iter
->endpoint() << "...\n";
128 // Set a deadline for the connect operation.
129 deadline_
.expires_after(boost::asio::chrono::seconds(60));
131 // Start the asynchronous connect operation.
132 socket_
.async_connect(endpoint_iter
->endpoint(),
133 boost::bind(&client::handle_connect
,
134 this, _1
, endpoint_iter
));
138 // There are no more endpoints to try. Shut down the client.
143 void handle_connect(const boost::system::error_code
& ec
,
144 tcp::resolver::results_type::iterator endpoint_iter
)
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())
154 std::cout
<< "Connect timed out\n";
156 // Try the next available endpoint.
157 start_connect(++endpoint_iter
);
160 // Check if the connect operation failed before the deadline expired.
163 std::cout
<< "Connect error: " << ec
.message() << "\n";
165 // We need to close the socket used in the previous connection attempt
166 // before starting a new one.
169 // Try the next available endpoint.
170 start_connect(++endpoint_iter
);
173 // Otherwise we have successfully established a connection.
176 std::cout
<< "Connected to " << endpoint_iter
->endpoint() << "\n";
178 // Start the input actor.
181 // Start the heartbeat actor.
188 // Set a deadline for the read operation.
189 deadline_
.expires_after(boost::asio::chrono::seconds(30));
191 // Start an asynchronous operation to read a newline-delimited message.
192 boost::asio::async_read_until(socket_
,
193 boost::asio::dynamic_buffer(input_buffer_
), '\n',
194 boost::bind(&client::handle_read
, this, _1
, _2
));
197 void handle_read(const boost::system::error_code
& ec
, std::size_t n
)
204 // Extract the newline-delimited message from the buffer.
205 std::string
line(input_buffer_
.substr(0, n
- 1));
206 input_buffer_
.erase(0, n
);
208 // Empty messages are heartbeats and so ignored.
211 std::cout
<< "Received: " << line
<< "\n";
218 std::cout
<< "Error on receive: " << ec
.message() << "\n";
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
));
234 void handle_write(const boost::system::error_code
& ec
)
241 // Wait 10 seconds before sending the next heartbeat.
242 heartbeat_timer_
.expires_after(boost::asio::chrono::seconds(10));
243 heartbeat_timer_
.async_wait(boost::bind(&client::start_write
, this));
247 std::cout
<< "Error on heartbeat: " << ec
.message() << "\n";
253 void check_deadline()
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.
261 if (deadline_
.expiry() <= steady_timer::clock_type::now())
263 // The deadline has passed. The socket is closed so that any outstanding
264 // asynchronous operations are cancelled.
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
270 deadline_
.expires_at(steady_timer::time_point::max());
273 // Put the actor back to sleep.
274 deadline_
.async_wait(boost::bind(&client::check_deadline
, this));
279 tcp::resolver::results_type endpoints_
;
281 std::string input_buffer_
;
282 steady_timer deadline_
;
283 steady_timer heartbeat_timer_
;
286 int main(int argc
, char* argv
[])
292 std::cerr
<< "Usage: client <host> <port>\n";
296 boost::asio::io_context io_context
;
297 tcp::resolver
r(io_context
);
298 client
c(io_context
);
300 c
.start(r
.resolve(argv
[1], argv
[2]));
304 catch (std::exception
& e
)
306 std::cerr
<< "Exception: " << e
.what() << "\n";