1 //
2 // blocking_tcp_client.cpp
3 // ~~~~~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2021 Christopher M. Kohlhoff (chris at kohlhoff dot com)
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/buffer.hpp>
12 #include <boost/asio/connect.hpp>
13 #include <boost/asio/io_context.hpp>
14 #include <boost/asio/ip/tcp.hpp>
15 #include <boost/asio/read_until.hpp>
16 #include <boost/system/system_error.hpp>
17 #include <boost/asio/write.hpp>
18 #include <cstdlib>
19 #include <iostream>
20 #include <string>
21
22 using boost::asio::ip::tcp;
23
24 //----------------------------------------------------------------------
25
26 //
27 // This class manages socket timeouts by running the io_context using the timed
28 // io_context::run_for() member function. Each asynchronous operation is given
29 // a timeout within which it must complete. The socket operations themselves
30 // use lambdas as completion handlers. For a given socket operation, the client
31 // object runs the io_context to block thread execution until the operation
32 // completes or the timeout is reached. If the io_context::run_for() function
33 // times out, the socket is closed and the outstanding asynchronous operation
34 // is cancelled.
35 //
36 class client
37 {
38 public:
connect(const std::string & host,const std::string & service,std::chrono::steady_clock::duration timeout)39 void connect(const std::string& host, const std::string& service,
40 std::chrono::steady_clock::duration timeout)
41 {
42 // Resolve the host name and service to a list of endpoints.
43 auto endpoints = tcp::resolver(io_context_).resolve(host, service);
44
45 // Start the asynchronous operation itself. The lambda that is used as a
46 // callback will update the error variable when the operation completes.
47 // The blocking_udp_client.cpp example shows how you can use std::bind
48 // rather than a lambda.
49 boost::system::error_code error;
50 boost::asio::async_connect(socket_, endpoints,
51 [&](const boost::system::error_code& result_error,
52 const tcp::endpoint& /*result_endpoint*/)
53 {
54 error = result_error;
55 });
56
57 // Run the operation until it completes, or until the timeout.
58 run(timeout);
59
60 // Determine whether a connection was successfully established.
61 if (error)
62 throw std::system_error(error);
63 }
64
read_line(std::chrono::steady_clock::duration timeout)65 std::string read_line(std::chrono::steady_clock::duration timeout)
66 {
67 // Start the asynchronous operation. The lambda that is used as a callback
68 // will update the error and n variables when the operation completes. The
69 // blocking_udp_client.cpp example shows how you can use std::bind rather
70 // than a lambda.
71 boost::system::error_code error;
72 std::size_t n = 0;
73 boost::asio::async_read_until(socket_,
74 boost::asio::dynamic_buffer(input_buffer_), '\n',
75 [&](const boost::system::error_code& result_error,
76 std::size_t result_n)
77 {
78 error = result_error;
79 n = result_n;
80 });
81
82 // Run the operation until it completes, or until the timeout.
83 run(timeout);
84
85 // Determine whether the read completed successfully.
86 if (error)
87 throw std::system_error(error);
88
89 std::string line(input_buffer_.substr(0, n - 1));
90 input_buffer_.erase(0, n);
91 return line;
92 }
93
write_line(const std::string & line,std::chrono::steady_clock::duration timeout)94 void write_line(const std::string& line,
95 std::chrono::steady_clock::duration timeout)
96 {
97 std::string data = line + "\n";
98
99 // Start the asynchronous operation itself. The lambda that is used as a
100 // callback will update the error variable when the operation completes.
101 // The blocking_udp_client.cpp example shows how you can use std::bind
102 // rather than a lambda.
103 boost::system::error_code error;
104 boost::asio::async_write(socket_, boost::asio::buffer(data),
105 [&](const boost::system::error_code& result_error,
106 std::size_t /*result_n*/)
107 {
108 error = result_error;
109 });
110
111 // Run the operation until it completes, or until the timeout.
112 run(timeout);
113
114 // Determine whether the read completed successfully.
115 if (error)
116 throw std::system_error(error);
117 }
118
119 private:
run(std::chrono::steady_clock::duration timeout)120 void run(std::chrono::steady_clock::duration timeout)
121 {
122 // Restart the io_context, as it may have been left in the "stopped" state
123 // by a previous operation.
124 io_context_.restart();
125
126 // Block until the asynchronous operation has completed, or timed out. If
127 // the pending asynchronous operation is a composed operation, the deadline
128 // applies to the entire operation, rather than individual operations on
129 // the socket.
130 io_context_.run_for(timeout);
131
132 // If the asynchronous operation completed successfully then the io_context
133 // would have been stopped due to running out of work. If it was not
134 // stopped, then the io_context::run_for call must have timed out.
135 if (!io_context_.stopped())
136 {
137 // Close the socket to cancel the outstanding asynchronous operation.
138 socket_.close();
139
140 // Run the io_context again until the operation completes.
141 io_context_.run();
142 }
143 }
144
145 boost::asio::io_context io_context_;
146 tcp::socket socket_{io_context_};
147 std::string input_buffer_;
148 };
149
150 //----------------------------------------------------------------------
151
main(int argc,char * argv[])152 int main(int argc, char* argv[])
153 {
154 try
155 {
156 if (argc != 4)
157 {
158 std::cerr << "Usage: blocking_tcp_client <host> <port> <message>\n";
159 return 1;
160 }
161
162 client c;
163 c.connect(argv[1], argv[2], std::chrono::seconds(10));
164
165 auto time_sent = std::chrono::steady_clock::now();
166
167 c.write_line(argv[3], std::chrono::seconds(10));
168
169 for (;;)
170 {
171 std::string line = c.read_line(std::chrono::seconds(10));
172
173 // Keep going until we get back the line that was sent.
174 if (line == argv[3])
175 break;
176 }
177
178 auto time_received = std::chrono::steady_clock::now();
179
180 std::cout << "Round trip time: ";
181 std::cout << std::chrono::duration_cast<
182 std::chrono::microseconds>(
183 time_received - time_sent).count();
184 std::cout << " microseconds\n";
185 }
186 catch (std::exception& e)
187 {
188 std::cerr << "Exception: " << e.what() << "\n";
189 }
190
191 return 0;
192 }
193