• 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 #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