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