• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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