1 //
2 // composed_4.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 <asio/bind_executor.hpp>
12 #include <asio/io_context.hpp>
13 #include <asio/ip/tcp.hpp>
14 #include <asio/use_future.hpp>
15 #include <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 asio::ip::tcp;
24 
25 // NOTE: This example requires the new 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 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 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 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(std::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(std::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 = 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 asio::bind_executor function.
102     std::size_t length = std::strlen(message);
103     if (length == 0)
104     {
105       asio::post(
106           asio::bind_executor(executor,
107             std::bind(std::forward<decltype(completion_handler)>(
108                 completion_handler), asio::error::invalid_argument)));
109     }
110     else
111     {
112       asio::async_write(socket,
113           asio::buffer(message, length),
114           asio::bind_executor(executor,
115             std::bind(std::forward<decltype(completion_handler)>(
116                 completion_handler), std::placeholders::_1)));
117     }
118   };
119 
120   // The 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 asio::async_initiate<
136     CompletionToken, void(std::error_code)>(
137       initiation, token, std::ref(socket), message);
138 }
139 
140 //------------------------------------------------------------------------------
141 
test_callback()142 void test_callback()
143 {
144   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 std::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   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, "", 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