]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // blocking_tcp_client.cpp | |
3 | // ~~~~~~~~~~~~~~~~~~~~~~~ | |
4 | // | |
5 | // Copyright (c) 2003-2016 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/connect.hpp> | |
12 | #include <boost/asio/deadline_timer.hpp> | |
13 | #include <boost/asio/io_service.hpp> | |
14 | #include <boost/asio/ip/tcp.hpp> | |
15 | #include <boost/asio/read_until.hpp> | |
16 | #include <boost/asio/streambuf.hpp> | |
17 | #include <boost/system/system_error.hpp> | |
18 | #include <boost/asio/write.hpp> | |
19 | #include <cstdlib> | |
20 | #include <iostream> | |
21 | #include <string> | |
22 | #include <boost/lambda/bind.hpp> | |
23 | #include <boost/lambda/lambda.hpp> | |
24 | ||
25 | using boost::asio::deadline_timer; | |
26 | using boost::asio::ip::tcp; | |
27 | using boost::lambda::bind; | |
28 | using boost::lambda::var; | |
29 | using boost::lambda::_1; | |
30 | ||
31 | //---------------------------------------------------------------------- | |
32 | ||
33 | // | |
34 | // This class manages socket timeouts by applying the concept of a deadline. | |
35 | // Each asynchronous operation is given a deadline by which it must complete. | |
36 | // Deadlines are enforced by an "actor" that persists for the lifetime of the | |
37 | // client object: | |
38 | // | |
39 | // +----------------+ | |
40 | // | | | |
41 | // | check_deadline |<---+ | |
42 | // | | | | |
43 | // +----------------+ | async_wait() | |
44 | // | | | |
45 | // +---------+ | |
46 | // | |
47 | // If the actor determines that the deadline has expired, the socket is closed | |
48 | // and any outstanding operations are consequently cancelled. The socket | |
49 | // operations themselves use boost::lambda function objects as completion | |
50 | // handlers. For a given socket operation, the client object runs the | |
51 | // io_service to block thread execution until the actor completes. | |
52 | // | |
53 | class client | |
54 | { | |
55 | public: | |
56 | client() | |
57 | : socket_(io_service_), | |
58 | deadline_(io_service_) | |
59 | { | |
60 | // No deadline is required until the first socket operation is started. We | |
61 | // set the deadline to positive infinity so that the actor takes no action | |
62 | // until a specific deadline is set. | |
63 | deadline_.expires_at(boost::posix_time::pos_infin); | |
64 | ||
65 | // Start the persistent actor that checks for deadline expiry. | |
66 | check_deadline(); | |
67 | } | |
68 | ||
69 | void connect(const std::string& host, const std::string& service, | |
70 | boost::posix_time::time_duration timeout) | |
71 | { | |
72 | // Resolve the host name and service to a list of endpoints. | |
73 | tcp::resolver::query query(host, service); | |
74 | tcp::resolver::iterator iter = tcp::resolver(io_service_).resolve(query); | |
75 | ||
76 | // Set a deadline for the asynchronous operation. As a host name may | |
77 | // resolve to multiple endpoints, this function uses the composed operation | |
78 | // async_connect. The deadline applies to the entire operation, rather than | |
79 | // individual connection attempts. | |
80 | deadline_.expires_from_now(timeout); | |
81 | ||
82 | // Set up the variable that receives the result of the asynchronous | |
83 | // operation. The error code is set to would_block to signal that the | |
84 | // operation is incomplete. Asio guarantees that its asynchronous | |
85 | // operations will never fail with would_block, so any other value in | |
86 | // ec indicates completion. | |
87 | boost::system::error_code ec = boost::asio::error::would_block; | |
88 | ||
89 | // Start the asynchronous operation itself. The boost::lambda function | |
90 | // object is used as a callback and will update the ec variable when the | |
91 | // operation completes. The blocking_udp_client.cpp example shows how you | |
92 | // can use boost::bind rather than boost::lambda. | |
93 | boost::asio::async_connect(socket_, iter, var(ec) = _1); | |
94 | ||
95 | // Block until the asynchronous operation has completed. | |
96 | do io_service_.run_one(); while (ec == boost::asio::error::would_block); | |
97 | ||
98 | // Determine whether a connection was successfully established. The | |
99 | // deadline actor may have had a chance to run and close our socket, even | |
100 | // though the connect operation notionally succeeded. Therefore we must | |
101 | // check whether the socket is still open before deciding if we succeeded | |
102 | // or failed. | |
103 | if (ec || !socket_.is_open()) | |
104 | throw boost::system::system_error( | |
105 | ec ? ec : boost::asio::error::operation_aborted); | |
106 | } | |
107 | ||
108 | std::string read_line(boost::posix_time::time_duration timeout) | |
109 | { | |
110 | // Set a deadline for the asynchronous operation. Since this function uses | |
111 | // a composed operation (async_read_until), the deadline applies to the | |
112 | // entire operation, rather than individual reads from the socket. | |
113 | deadline_.expires_from_now(timeout); | |
114 | ||
115 | // Set up the variable that receives the result of the asynchronous | |
116 | // operation. The error code is set to would_block to signal that the | |
117 | // operation is incomplete. Asio guarantees that its asynchronous | |
118 | // operations will never fail with would_block, so any other value in | |
119 | // ec indicates completion. | |
120 | boost::system::error_code ec = boost::asio::error::would_block; | |
121 | ||
122 | // Start the asynchronous operation itself. The boost::lambda function | |
123 | // object is used as a callback and will update the ec variable when the | |
124 | // operation completes. The blocking_udp_client.cpp example shows how you | |
125 | // can use boost::bind rather than boost::lambda. | |
126 | boost::asio::async_read_until(socket_, input_buffer_, '\n', var(ec) = _1); | |
127 | ||
128 | // Block until the asynchronous operation has completed. | |
129 | do io_service_.run_one(); while (ec == boost::asio::error::would_block); | |
130 | ||
131 | if (ec) | |
132 | throw boost::system::system_error(ec); | |
133 | ||
134 | std::string line; | |
135 | std::istream is(&input_buffer_); | |
136 | std::getline(is, line); | |
137 | return line; | |
138 | } | |
139 | ||
140 | void write_line(const std::string& line, | |
141 | boost::posix_time::time_duration timeout) | |
142 | { | |
143 | std::string data = line + "\n"; | |
144 | ||
145 | // Set a deadline for the asynchronous operation. Since this function uses | |
146 | // a composed operation (async_write), the deadline applies to the entire | |
147 | // operation, rather than individual writes to the socket. | |
148 | deadline_.expires_from_now(timeout); | |
149 | ||
150 | // Set up the variable that receives the result of the asynchronous | |
151 | // operation. The error code is set to would_block to signal that the | |
152 | // operation is incomplete. Asio guarantees that its asynchronous | |
153 | // operations will never fail with would_block, so any other value in | |
154 | // ec indicates completion. | |
155 | boost::system::error_code ec = boost::asio::error::would_block; | |
156 | ||
157 | // Start the asynchronous operation itself. The boost::lambda function | |
158 | // object is used as a callback and will update the ec variable when the | |
159 | // operation completes. The blocking_udp_client.cpp example shows how you | |
160 | // can use boost::bind rather than boost::lambda. | |
161 | boost::asio::async_write(socket_, boost::asio::buffer(data), var(ec) = _1); | |
162 | ||
163 | // Block until the asynchronous operation has completed. | |
164 | do io_service_.run_one(); while (ec == boost::asio::error::would_block); | |
165 | ||
166 | if (ec) | |
167 | throw boost::system::system_error(ec); | |
168 | } | |
169 | ||
170 | private: | |
171 | void check_deadline() | |
172 | { | |
173 | // Check whether the deadline has passed. We compare the deadline against | |
174 | // the current time since a new asynchronous operation may have moved the | |
175 | // deadline before this actor had a chance to run. | |
176 | if (deadline_.expires_at() <= deadline_timer::traits_type::now()) | |
177 | { | |
178 | // The deadline has passed. The socket is closed so that any outstanding | |
179 | // asynchronous operations are cancelled. This allows the blocked | |
180 | // connect(), read_line() or write_line() functions to return. | |
181 | boost::system::error_code ignored_ec; | |
182 | socket_.close(ignored_ec); | |
183 | ||
184 | // There is no longer an active deadline. The expiry is set to positive | |
185 | // infinity so that the actor takes no action until a new deadline is set. | |
186 | deadline_.expires_at(boost::posix_time::pos_infin); | |
187 | } | |
188 | ||
189 | // Put the actor back to sleep. | |
190 | deadline_.async_wait(bind(&client::check_deadline, this)); | |
191 | } | |
192 | ||
193 | boost::asio::io_service io_service_; | |
194 | tcp::socket socket_; | |
195 | deadline_timer deadline_; | |
196 | boost::asio::streambuf input_buffer_; | |
197 | }; | |
198 | ||
199 | //---------------------------------------------------------------------- | |
200 | ||
201 | int main(int argc, char* argv[]) | |
202 | { | |
203 | try | |
204 | { | |
205 | if (argc != 4) | |
206 | { | |
207 | std::cerr << "Usage: blocking_tcp <host> <port> <message>\n"; | |
208 | return 1; | |
209 | } | |
210 | ||
211 | client c; | |
212 | c.connect(argv[1], argv[2], boost::posix_time::seconds(10)); | |
213 | ||
214 | boost::posix_time::ptime time_sent = | |
215 | boost::posix_time::microsec_clock::universal_time(); | |
216 | ||
217 | c.write_line(argv[3], boost::posix_time::seconds(10)); | |
218 | ||
219 | for (;;) | |
220 | { | |
221 | std::string line = c.read_line(boost::posix_time::seconds(10)); | |
222 | ||
223 | // Keep going until we get back the line that was sent. | |
224 | if (line == argv[3]) | |
225 | break; | |
226 | } | |
227 | ||
228 | boost::posix_time::ptime time_received = | |
229 | boost::posix_time::microsec_clock::universal_time(); | |
230 | ||
231 | std::cout << "Round trip time: "; | |
232 | std::cout << (time_received - time_sent).total_microseconds(); | |
233 | std::cout << " microseconds\n"; | |
234 | } | |
235 | catch (std::exception& e) | |
236 | { | |
237 | std::cerr << "Exception: " << e.what() << "\n"; | |
238 | } | |
239 | ||
240 | return 0; | |
241 | } |