1 //
2 // blocking_udp_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 
11 #include <boost/asio/buffer.hpp>
12 #include <boost/asio/io_context.hpp>
13 #include <boost/asio/ip/udp.hpp>
14 #include <cstdlib>
15 #include <functional>
16 #include <iostream>
17 
18 using boost::asio::ip::udp;
19 using std::placeholders::_1;
20 using std::placeholders::_2;
21 
22 //----------------------------------------------------------------------
23 
24 //
25 // This class manages socket timeouts by running the io_context using the timed
26 // io_context::run_for() member function. Each asynchronous operation is given
27 // a timeout within which it must complete. The socket operations themselves
28 // use std::bind to specify the completion handler:
29 //
30 //   +---------------+
31 //   |               |
32 //   |    receive    |
33 //   |               |
34 //   +---------------+
35 //           |
36 //  async_-  |    +----------------+
37 // receive() |    |                |
38 //           +--->| handle_receive |
39 //                |                |
40 //                +----------------+
41 //
42 // For a given socket operation, the client object runs the io_context to block
43 // thread execution until the operation completes or the timeout is reached. If
44 // the io_context::run_for() function times out, the socket is closed and the
45 // outstanding asynchronous operation is cancelled.
46 //
47 class client
48 {
49 public:
client(const udp::endpoint & listen_endpoint)50   client(const udp::endpoint& listen_endpoint)
51     : socket_(io_context_, listen_endpoint)
52   {
53   }
54 
receive(const boost::asio::mutable_buffer & buffer,std::chrono::steady_clock::duration timeout,boost::system::error_code & error)55   std::size_t receive(const boost::asio::mutable_buffer& buffer,
56       std::chrono::steady_clock::duration timeout,
57       boost::system::error_code& error)
58   {
59     // Start the asynchronous operation. The handle_receive function used as a
60     // callback will update the error and length variables.
61     std::size_t length = 0;
62     socket_.async_receive(boost::asio::buffer(buffer),
63         std::bind(&client::handle_receive, _1, _2, &error, &length));
64 
65     // Run the operation until it completes, or until the timeout.
66     run(timeout);
67 
68     return length;
69   }
70 
71 private:
run(std::chrono::steady_clock::duration timeout)72   void run(std::chrono::steady_clock::duration timeout)
73   {
74     // Restart the io_context, as it may have been left in the "stopped" state
75     // by a previous operation.
76     io_context_.restart();
77 
78     // Block until the asynchronous operation has completed, or timed out. If
79     // the pending asynchronous operation is a composed operation, the deadline
80     // applies to the entire operation, rather than individual operations on
81     // the socket.
82     io_context_.run_for(timeout);
83 
84     // If the asynchronous operation completed successfully then the io_context
85     // would have been stopped due to running out of work. If it was not
86     // stopped, then the io_context::run_for call must have timed out.
87     if (!io_context_.stopped())
88     {
89       // Cancel the outstanding asynchronous operation.
90       socket_.cancel();
91 
92       // Run the io_context again until the operation completes.
93       io_context_.run();
94     }
95   }
96 
handle_receive(const boost::system::error_code & error,std::size_t length,boost::system::error_code * out_error,std::size_t * out_length)97   static void handle_receive(
98       const boost::system::error_code& error, std::size_t length,
99       boost::system::error_code* out_error, std::size_t* out_length)
100   {
101     *out_error = error;
102     *out_length = length;
103   }
104 
105 private:
106   boost::asio::io_context io_context_;
107   udp::socket socket_;
108 };
109 
110 //----------------------------------------------------------------------
111 
main(int argc,char * argv[])112 int main(int argc, char* argv[])
113 {
114   try
115   {
116     using namespace std; // For atoi.
117 
118     if (argc != 3)
119     {
120       std::cerr << "Usage: blocking_udp_client <listen_addr> <listen_port>\n";
121       return 1;
122     }
123 
124     udp::endpoint listen_endpoint(
125         boost::asio::ip::make_address(argv[1]),
126         std::atoi(argv[2]));
127 
128     client c(listen_endpoint);
129 
130     for (;;)
131     {
132       char data[1024];
133       boost::system::error_code error;
134       std::size_t n = c.receive(boost::asio::buffer(data),
135           std::chrono::seconds(10), error);
136 
137       if (error)
138       {
139         std::cout << "Receive error: " << error.message() << "\n";
140       }
141       else
142       {
143         std::cout << "Received: ";
144         std::cout.write(data, n);
145         std::cout << "\n";
146       }
147     }
148   }
149   catch (std::exception& e)
150   {
151     std::cerr << "Exception: " << e.what() << "\n";
152   }
153 
154   return 0;
155 }
156