]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // |
2 | // blocking_tcp_client.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/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: | |
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 | ||
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 | ||
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: | |
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 | ||
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 | } |