• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  //
2  // composed_4.cpp
3  // ~~~~~~~~~~~~~~
4  //
5  // Copyright (c) 2003-2020 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/bind_executor.hpp>
12  #include <boost/asio/io_context.hpp>
13  #include <boost/asio/ip/tcp.hpp>
14  #include <boost/asio/use_future.hpp>
15  #include <boost/asio/write.hpp>
16  #include <cstring>
17  #include <functional>
18  #include <iostream>
19  #include <string>
20  #include <type_traits>
21  #include <utility>
22  
23  using boost::asio::ip::tcp;
24  
25  // NOTE: This example requires the new boost::asio::async_initiate function. For
26  // an example that works with the Networking TS style of completion tokens,
27  // please see an older version of asio.
28  
29  //------------------------------------------------------------------------------
30  
31  // In this composed operation we repackage an existing operation, but with a
32  // different completion handler signature. We will also intercept an empty
33  // message as an invalid argument, and propagate the corresponding error to the
34  // user. The asynchronous operation requirements are met by delegating
35  // responsibility to the underlying operation.
36  
37  template <typename CompletionToken>
async_write_message(tcp::socket & socket,const char * message,CompletionToken && token)38  auto async_write_message(tcp::socket& socket,
39      const char* message, CompletionToken&& token)
40    // The return type of the initiating function is deduced from the combination
41    // of CompletionToken type and the completion handler's signature. When the
42    // completion token is a simple callback, the return type is always void.
43    // In this example, when the completion token is boost::asio::yield_context
44    // (used for stackful coroutines) the return type would be also be void, as
45    // there is no non-error argument to the completion handler. When the
46    // completion token is boost::asio::use_future it would be std::future<void>.
47    //
48    // In C++14 we can omit the return type as it is automatically deduced from
49    // the return type of boost::asio::async_initiate.
50  {
51    // In addition to determining the mechanism by which an asynchronous
52    // operation delivers its result, a completion token also determines the time
53    // when the operation commences. For example, when the completion token is a
54    // simple callback the operation commences before the initiating function
55    // returns. However, if the completion token's delivery mechanism uses a
56    // future, we might instead want to defer initiation of the operation until
57    // the returned future object is waited upon.
58    //
59    // To enable this, when implementing an asynchronous operation we must
60    // package the initiation step as a function object. The initiation function
61    // object's call operator is passed the concrete completion handler produced
62    // by the completion token. This completion handler matches the asynchronous
63    // operation's completion handler signature, which in this example is:
64    //
65    //   void(boost::system::error_code error)
66    //
67    // The initiation function object also receives any additional arguments
68    // required to start the operation. (Note: We could have instead passed these
69    // arguments in the lambda capture set. However, we should prefer to
70    // propagate them as function call arguments as this allows the completion
71    // token to optimise how they are passed. For example, a lazy future which
72    // defers initiation would need to make a decay-copy of the arguments, but
73    // when using a simple callback the arguments can be trivially forwarded
74    // straight through.)
75    auto initiation = [](auto&& completion_handler,
76        tcp::socket& socket, const char* message)
77    {
78      // The post operation has a completion handler signature of:
79      //
80      //   void()
81      //
82      // and the async_write operation has a completion handler signature of:
83      //
84      //   void(boost::system::error_code error, std::size n)
85      //
86      // Both of these operations' completion handler signatures differ from our
87      // operation's completion handler signature. We will adapt our completion
88      // handler to these signatures by using std::bind, which drops the
89      // additional arguments.
90      //
91      // However, it is essential to the correctness of our composed operation
92      // that we preserve the executor of the user-supplied completion handler.
93      // The std::bind function will not do this for us, so we must do this by
94      // first obtaining the completion handler's associated executor (defaulting
95      // to the I/O executor - in this case the executor of the socket - if the
96      // completion handler does not have its own) ...
97      auto executor = boost::asio::get_associated_executor(
98          completion_handler, socket.get_executor());
99  
100      // ... and then binding this executor to our adapted completion handler
101      // using the boost::asio::bind_executor function.
102      std::size_t length = std::strlen(message);
103      if (length == 0)
104      {
105        boost::asio::post(
106            boost::asio::bind_executor(executor,
107              std::bind(std::forward<decltype(completion_handler)>(
108                  completion_handler), boost::asio::error::invalid_argument)));
109      }
110      else
111      {
112        boost::asio::async_write(socket,
113            boost::asio::buffer(message, length),
114            boost::asio::bind_executor(executor,
115              std::bind(std::forward<decltype(completion_handler)>(
116                  completion_handler), std::placeholders::_1)));
117      }
118    };
119  
120    // The boost::asio::async_initiate function takes:
121    //
122    // - our initiation function object,
123    // - the completion token,
124    // - the completion handler signature, and
125    // - any additional arguments we need to initiate the operation.
126    //
127    // It then asks the completion token to create a completion handler (i.e. a
128    // callback) with the specified signature, and invoke the initiation function
129    // object with this completion handler as well as the additional arguments.
130    // The return value of async_initiate is the result of our operation's
131    // initiating function.
132    //
133    // Note that we wrap non-const reference arguments in std::reference_wrapper
134    // to prevent incorrect decay-copies of these objects.
135    return boost::asio::async_initiate<
136      CompletionToken, void(boost::system::error_code)>(
137        initiation, token, std::ref(socket), message);
138  }
139  
140  //------------------------------------------------------------------------------
141  
test_callback()142  void test_callback()
143  {
144    boost::asio::io_context io_context;
145  
146    tcp::acceptor acceptor(io_context, {tcp::v4(), 55555});
147    tcp::socket socket = acceptor.accept();
148  
149    // Test our asynchronous operation using a lambda as a callback.
150    async_write_message(socket, "",
151        [](const boost::system::error_code& error)
152        {
153          if (!error)
154          {
155            std::cout << "Message sent\n";
156          }
157          else
158          {
159            std::cout << "Error: " << error.message() << "\n";
160          }
161        });
162  
163    io_context.run();
164  }
165  
166  //------------------------------------------------------------------------------
167  
test_future()168  void test_future()
169  {
170    boost::asio::io_context io_context;
171  
172    tcp::acceptor acceptor(io_context, {tcp::v4(), 55555});
173    tcp::socket socket = acceptor.accept();
174  
175    // Test our asynchronous operation using the use_future completion token.
176    // This token causes the operation's initiating function to return a future,
177    // which may be used to synchronously wait for the result of the operation.
178    std::future<void> f = async_write_message(
179        socket, "", boost::asio::use_future);
180  
181    io_context.run();
182  
183    try
184    {
185      // Get the result of the operation.
186      f.get();
187      std::cout << "Message sent\n";
188    }
189    catch (const std::exception& e)
190    {
191      std::cout << "Exception: " << e.what() << "\n";
192    }
193  }
194  
195  //------------------------------------------------------------------------------
196  
main()197  int main()
198  {
199    test_callback();
200    test_future();
201  }
202