blocking_token_tcp_client.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. //
  2. // blocking_token_tcp_client.cpp
  3. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. //
  5. // Copyright (c) 2003-2019 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. #include <boost/asio/connect.hpp>
  11. #include <boost/asio/io_context.hpp>
  12. #include <boost/asio/ip/tcp.hpp>
  13. #include <boost/asio/read_until.hpp>
  14. #include <boost/asio/streambuf.hpp>
  15. #include <boost/system/system_error.hpp>
  16. #include <boost/asio/write.hpp>
  17. #include <cstdlib>
  18. #include <iostream>
  19. #include <memory>
  20. #include <string>
  21. using boost::asio::ip::tcp;
  22. // We will use our sockets only with an io_context.
  23. using tcp_socket = boost::asio::basic_stream_socket<
  24. tcp, boost::asio::io_context::executor_type>;
  25. //----------------------------------------------------------------------
  26. // A custom completion token that makes asynchronous operations behave as
  27. // though they are blocking calls with a timeout.
  28. struct close_after
  29. {
  30. close_after(std::chrono::steady_clock::duration t, tcp_socket& s)
  31. : timeout_(t), socket_(s)
  32. {
  33. }
  34. // The maximum time to wait for an asynchronous operation to complete.
  35. std::chrono::steady_clock::duration timeout_;
  36. // The socket to be closed if the operation does not complete in time.
  37. tcp_socket& socket_;
  38. };
  39. namespace boost {
  40. namespace asio {
  41. // The async_result template is specialised to allow the close_after token to
  42. // be used with asynchronous operations that have a completion signature of
  43. // void(error_code, T). Generalising this for all completion signature forms is
  44. // left as an exercise for the reader.
  45. template <typename T>
  46. class async_result<close_after, void(boost::system::error_code, T)>
  47. {
  48. public:
  49. // An asynchronous operation's initiating function automatically creates an
  50. // completion_handler_type object from the token. This function object is
  51. // then called on completion of the asynchronous operation.
  52. class completion_handler_type
  53. {
  54. public:
  55. completion_handler_type(const close_after& token)
  56. : token_(token)
  57. {
  58. }
  59. void operator()(const boost::system::error_code& error, T t)
  60. {
  61. *error_ = error;
  62. *t_ = t;
  63. }
  64. private:
  65. friend class async_result;
  66. close_after token_;
  67. boost::system::error_code* error_;
  68. T* t_;
  69. };
  70. // The async_result constructor associates the completion handler object with
  71. // the result of the initiating function.
  72. explicit async_result(completion_handler_type& h)
  73. : timeout_(h.token_.timeout_),
  74. socket_(h.token_.socket_)
  75. {
  76. h.error_ = &error_;
  77. h.t_ = &t_;
  78. }
  79. // The return_type typedef determines the result type of the asynchronous
  80. // operation's initiating function.
  81. typedef T return_type;
  82. // The get() function is used to obtain the result of the asynchronous
  83. // operation's initiating function. For the close_after completion token, we
  84. // use this function to run the io_context until the operation is complete.
  85. return_type get()
  86. {
  87. boost::asio::io_context& io_context = socket_.get_executor().context();
  88. // Restart the io_context, as it may have been left in the "stopped" state
  89. // by a previous operation.
  90. io_context.restart();
  91. // Block until the asynchronous operation has completed, or timed out. If
  92. // the pending asynchronous operation is a composed operation, the deadline
  93. // applies to the entire operation, rather than individual operations on
  94. // the socket.
  95. io_context.run_for(timeout_);
  96. // If the asynchronous operation completed successfully then the io_context
  97. // would have been stopped due to running out of work. If it was not
  98. // stopped, then the io_context::run_for call must have timed out and the
  99. // operation is still incomplete.
  100. if (!io_context.stopped())
  101. {
  102. // Close the socket to cancel the outstanding asynchronous operation.
  103. socket_.close();
  104. // Run the io_context again until the operation completes.
  105. io_context.run();
  106. }
  107. // If the operation failed, throw an exception. Otherwise return the result.
  108. return error_ ? throw std::system_error(error_) : t_;
  109. }
  110. private:
  111. std::chrono::steady_clock::duration timeout_;
  112. tcp_socket& socket_;
  113. boost::system::error_code error_;
  114. T t_;
  115. };
  116. } // namespace asio
  117. } // namespace boost
  118. //----------------------------------------------------------------------
  119. int main(int argc, char* argv[])
  120. {
  121. try
  122. {
  123. if (argc != 4)
  124. {
  125. std::cerr << "Usage: blocking_tcp_client <host> <port> <message>\n";
  126. return 1;
  127. }
  128. boost::asio::io_context io_context;
  129. // Resolve the host name and service to a list of endpoints.
  130. auto endpoints = tcp::resolver(io_context).resolve(argv[1], argv[2]);
  131. tcp_socket socket(io_context);
  132. // Run an asynchronous connect operation with a timeout.
  133. boost::asio::async_connect(socket, endpoints,
  134. close_after(std::chrono::seconds(10), socket));
  135. auto time_sent = std::chrono::steady_clock::now();
  136. // Run an asynchronous write operation with a timeout.
  137. std::string msg = argv[3] + std::string("\n");
  138. boost::asio::async_write(socket, boost::asio::buffer(msg),
  139. close_after(std::chrono::seconds(10), socket));
  140. for (std::string input_buffer;;)
  141. {
  142. // Run an asynchronous read operation with a timeout.
  143. std::size_t n = boost::asio::async_read_until(socket,
  144. boost::asio::dynamic_buffer(input_buffer), '\n',
  145. close_after(std::chrono::seconds(10), socket));
  146. std::string line(input_buffer.substr(0, n - 1));
  147. input_buffer.erase(0, n);
  148. // Keep going until we get back the line that was sent.
  149. if (line == argv[3])
  150. break;
  151. }
  152. auto time_received = std::chrono::steady_clock::now();
  153. std::cout << "Round trip time: ";
  154. std::cout << std::chrono::duration_cast<
  155. std::chrono::microseconds>(
  156. time_received - time_sent).count();
  157. std::cout << " microseconds\n";
  158. }
  159. catch (std::exception& e)
  160. {
  161. std::cerr << "Exception: " << e.what() << "\n";
  162. }
  163. return 0;
  164. }