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