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