diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk index 2560fc6..5f0ae6b 100644 --- a/doc/qbk/00_main.qbk +++ b/doc/qbk/00_main.qbk @@ -29,8 +29,6 @@ [template beastconceptslink[id term][@boost:/libs/beast/doc/html/beast/concepts/[id].html [term]]] [template mqttlink[id text][@https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc[id] [text]]] -[def __ASIO_PER_OP_CANCELLATION__ [@boost:/doc/html/boost_asio/overview/core/cancellation.html Per-Operation Cancellation]] - [def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers CompletionToken]] [def __ExecutionContext__ [reflink2 ExecutionContext ExecutionContext]] [def __StreamType__ [reflink2 StreamType StreamType]] @@ -38,7 +36,12 @@ [def __Boost__ [@https://www.boost.org/ Boost]] [def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]] -[def __Self__ [async_mqtt5]] +[def __ASIO_PER_OP_CANCELLATION__ [@boost:/doc/html/boost_asio/overview/core/cancellation.html Per-Operation Cancellation]] +[def __POST__ [@boost:doc/html/boost_asio/reference/post.html `boost::asio::post`]] +[def __CO_SPAWN__ [@boost:/doc/html/boost_asio/reference/co_spawn.html `boost::asio::co_spawn`]] +[def __USE_AWAITABLE__ [@boost:/doc/html/boost_asio/reference/use_awaitable.html `boost::asio::use_awaitable`]] + +[def __Self__ [reflink2 mqtt_client `mqtt_client`]] [/ MQTT ] [def __MQTT__ [@https://mqtt.org/ MQTT]] @@ -89,6 +92,8 @@ [def __REASON_CODES__ [reflink2 Reason_codes `Reason Codes`]] [def __ERROR_HANDLING__ [reflink2 Error_handling `Error handling`]] + [include 01_intro.qbk] +[include 02_examples.qbk] [include reference/reference.qbk] diff --git a/doc/qbk/02_examples.qbk b/doc/qbk/02_examples.qbk new file mode 100644 index 0000000..d95f403 --- /dev/null +++ b/doc/qbk/02_examples.qbk @@ -0,0 +1,32 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:async_examples Compatibility with Boost.Asio] +The __Self__ is built upon __Asio__ and thus follows the same principles. +This section illustrates the usage of __Self__ async +functions with different __CompletionToken__. + +# [link async_mqtt5.async_examples.callbacks Async functions with callbacks] +# [link async_mqtt5.async_examples.cpp20_coroutines Async functions with C++20 coroutines] + + +[section:callbacks Async functions with callbacks] +This example demonstrates how to use __Self__ asynchrous functions with callbacks. + +[import ../../example/callbacks.cpp] +[callbacks_examples] +[endsect] + +[section:cpp20_coroutines Async functions with C++20 coroutines] +This example demonstrates how to use __Self__ asynchrous functions with C++20 coroutines +using __USE_AWAITABLE__ and __CO_SPAWN__. + +[import ../../example/cpp20_coroutines.cpp] +[cpp20_coroutines_examples] +[endsect] + +[endsect] diff --git a/doc/qbk/reference/Error_handling.qbk b/doc/qbk/reference/Error_handling.qbk index bd3b814..1170ab8 100644 --- a/doc/qbk/reference/Error_handling.qbk +++ b/doc/qbk/reference/Error_handling.qbk @@ -20,6 +20,11 @@ may complete with, along with the reasons for their occurrence. with the __CompletionToken__ provided and the corresponding cancellation signal is emitted, the operation will also finish with this error code (see __ASIO_PER_OP_CANCELLATION__). ]] + [[`boost::asio::no_recovery`] [ + An non-recoverable error occurred during the attempt by the [reflink2 mqtt_client `mqtt_client`] + to establish a connection with the Broker. The cause of this error may be attributed to the connection + related parameters used during the initialization of the [reflink2 mqtt_client `mqtt_client`]. + ]] [[`boost::asio::experimental::error::channel_cancelled`] [ This error occurs in scenarios identical to those causing `boost::asio::error::operation_aborted` error code but it is exclusive to completion handlers associated with [refmem mqtt_client async_receive] calls. diff --git a/example/callbacks.cpp b/example/callbacks.cpp new file mode 100644 index 0000000..92dce53 --- /dev/null +++ b/example/callbacks.cpp @@ -0,0 +1,114 @@ +//[callbacks_examples + +#include + +#include + +#include + +#include + +namespace asio = boost::asio; + +using stream_type = asio::ip::tcp::socket; +using client_type = async_mqtt5::mqtt_client; +/** + * This function showcases how to call each asynchronous function + * in mqtt_client using callbacks. Note that this example is not + * intended for direct execution, as the async_disconnect call + * will promptly close the client. + */ +void run_with_callbacks(client_type& client) { + // Publish an Application Message with QoS 0. + client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, + // Callback with signature void (error_code) + [](async_mqtt5::error_code ec) { + std::cout << "error_code: " << ec.message() << std::endl; + } + ); + + // Publish an Application Message with QoS 1. + client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + // Callback with signature void (error_code, reason_code, puback_props) + [](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::puback_props) { + std::cout << "error_code: " << ec.message() << std::endl; + std::cout << "reason_code: " << rc.message() << std::endl; + } + ); + + // Publish an Application Message with QoS 2. + client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, + // Callback with signature (error_code, reason_code, pubcomp_props) + [](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::pubcomp_props) { + std::cout << "error_code: " << ec.message() << std::endl; + std::cout << "reason_code: " << rc.message() << std::endl; + } + ); + + // Subscribe to a single Topic. + client.async_subscribe( + { "test/mqtt-test", { async_mqtt5::qos_e::exactly_once } }, async_mqtt5::subscribe_props {}, + // Callback with signature void (error_code, std::vector, suback_props) + [](async_mqtt5::error_code ec, + std::vector codes, async_mqtt5::suback_props + ) { + std::cout << "subscribe error_code: " << ec.message() << std::endl; + std::cout << "subscribe reason_code: " << codes[0].message() << std::endl; + } + ); + + // Receive an Application Message. + client.async_receive( + // Callback with signature void (error_code, std::string, std::string, publish_props) + [] ( + async_mqtt5::error_code ec, std::string topic, + std::string payload, async_mqtt5::publish_props + ) { + std::cout << "topic: " << topic << std::endl; + std::cout << "payload: " << payload << std::endl; + } + ); + + // Unsubscribe from the Topic. + client.async_unsubscribe("test/mqtt-test", async_mqtt5::unsubscribe_props {}, + //Callback with signature void (error_code, std::vector, unsuback_props) + [](async_mqtt5::error_code ec, + std::vector codes, async_mqtt5::unsuback_props + ) { + std::cout << "unsubscribe error_code: " << ec.message() << std::endl; + std::cout << "unsubscribe reason_code: " << codes[0].message() << std::endl; + } + ); + + // Disconnect the Client. + client.async_disconnect( + async_mqtt5::disconnect_rc_e::disconnect_with_will_message, + async_mqtt5::disconnect_props {}, + // Callback with signature void (error_code) + [](async_mqtt5::error_code) {} + ); + +} + +int main(int argc, char** argv) { + asio::io_context ioc; + + // Make an instance of mqtt_client. Establish a TCP connection with the Broker. + client_type c(ioc.get_executor(), ""); + + c.credentials("test-client", "username", "password") + .brokers("mqtt.broker", 1883) + .run(); + + run_with_callbacks(c); + + ioc.run(); +} + +//] diff --git a/example/cpp20_coroutines.cpp b/example/cpp20_coroutines.cpp new file mode 100644 index 0000000..d21df63 --- /dev/null +++ b/example/cpp20_coroutines.cpp @@ -0,0 +1,190 @@ +//[cpp20_coroutines_examples + +#include +#include +#include +#include +#include + +#include + +#include + +namespace asio = boost::asio; + +using stream_type = asio::ip::tcp::socket; +using client_type = async_mqtt5::mqtt_client; + +/** + * An example of a coroutine. It must have a return type of boost::asio::awaitable. + * When an asynchronous function is called, the coroutine is suspended. + * After the asynchronous operation finishes, the coroutine resumes from the point it was suspended. + * + * In this example, each asynchronous function is invoked with boost::asio::use_awaitable completion token. + * When using this completion token, co_await will throw exceptions instead of returning an error code. + * If you do not wish to throw exceptions, refer to the following nothrow_awaitable and nothrow_coroutine() example. + */ +asio::awaitable coroutine(client_type& client) { + // Publish an Application Message with QoS 0. + // The handler signature for this function is void (error_code). + // However, when using asio::use_awaitable as a completion token, + // the error_code is not returned but thrown as an exception if an error occurrs. + co_await client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + asio::use_awaitable + ); + + // Publish an Application Message with QoS 1. + // The handler signature for this function is void (error_code, reason_code, puback_props). + // With asio::use_awaitable as a completion token, the co_await will return reason_code and puback_props. + auto [puback_rc, puback_props] = co_await client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + asio::use_awaitable + ); + + // Publish an Application Message with QoS 2. + // The handler signature for this function is void (error_code, reason_code, pubcomp_props). + // With asio::use_awaitable as a completion token, the co_await will return reason_code and pubcomp_props. + auto [pubcomp_rc, pubcomp_props] = co_await client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + asio::use_awaitable + ); + + auto sub_topic = async_mqtt5::subscribe_topic { + "test/mqtt-test", { + async_mqtt5::qos_e::exactly_once, + async_mqtt5::subscribe_options::no_local_e::no, + async_mqtt5::subscribe_options::retain_as_published_e::retain, + async_mqtt5::subscribe_options::retain_handling_e::send + } + }; + + // Subscribe to a single Topic. + // The handler signature for this function is void (error_code, std::vector, suback_props). + // With asio::use_awaitable as a completion token, the co_await + // will return std::vector and suback_props. + auto [sub_codes, sub_props] = co_await client.async_subscribe( + sub_topic, async_mqtt5::subscribe_props {}, asio::use_awaitable + ); + + // Receive an Application Message. + // The co_await call will return std::string (topic), std::string (payload) and publish_props. + // Note: the coroutine will be suspended until an Application Message is ready to be received + // or an error has occurred. In theory, the coroutine could be suspended indefinitely. + // Avoid calling this if you have not successfully subscribed to a Topic. + auto [topic, payload, publish_props] = co_await client.async_receive(asio::use_awaitable); + + // Unsubscribe from the Topic. + // The handler signature for this function is void (error_code, std::vector, unsuback_props). + // With asio::use_awaitable as a completion token, the co_await + // will return std::vector and unsuback_props. + auto [unsub_codes, unsub_props] = co_await client.async_unsubscribe( + std::vector{ "test/mqtt-test" }, async_mqtt5::unsubscribe_props {}, + asio::use_awaitable + ); + + // Disconnect the Client. + // With asio::use_awaitable as a completion token and void (error_code) as the completion signature, + // the co_await has nothing to return. + co_await client.async_disconnect( + async_mqtt5::disconnect_rc_e::disconnect_with_will_message, + async_mqtt5::disconnect_props {}, + asio::use_awaitable + ); + + co_return; +} + +/** + * A modified completion token. Using this completion token instead of asio::use_awaitable + * will prevent co_await from throwing exceptions. Instead, co_await will return the error code + * along with other values specified in the handler signature. + */ +constexpr auto nothrow_awaitable = asio::as_tuple(asio::use_awaitable); + +/** + * In this coroutine, each asynchronous function is called with nothrow_awaitable completion token. + * Unlike the asio::use_awaitable completion token, nothrow_awaitable does not throw an exception + * when an error has occurred. Instead, each co_await will return an error_code, similar to + * the behavior of using callbacks. + */ +asio::awaitable nothrow_coroutine(client_type& client) { + async_mqtt5::error_code ec; + async_mqtt5::reason_code rc; + + std::tie(ec) = co_await client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + nothrow_awaitable + ); + + async_mqtt5::puback_props puback_props; + std::tie(ec, rc, puback_props) = co_await client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + nothrow_awaitable + ); + + async_mqtt5::pubcomp_props pubcomp_props; + std::tie(ec, rc, pubcomp_props) = co_await client.async_publish( + "test/mqtt-test", "Hello world!", + async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, + nothrow_awaitable + ); + + auto sub_topic = async_mqtt5::subscribe_topic{ + "test/mqtt-test", { + async_mqtt5::qos_e::exactly_once, + async_mqtt5::subscribe_options::no_local_e::no, + async_mqtt5::subscribe_options::retain_as_published_e::retain, + async_mqtt5::subscribe_options::retain_handling_e::send + } + }; + + std::vector rcs; + async_mqtt5::suback_props suback_props; + std::tie(ec, rcs, suback_props) = co_await client.async_subscribe( + sub_topic, async_mqtt5::subscribe_props {}, nothrow_awaitable + ); + + std::string topic, payload; + async_mqtt5::publish_props publish_props; + std::tie(ec, topic, payload, publish_props) = co_await client.async_receive(nothrow_awaitable); + + async_mqtt5::unsuback_props unsuback_props; + std::tie(ec, rcs, unsuback_props) = co_await client.async_unsubscribe( + std::vector{ "test/mqtt-test" }, async_mqtt5::unsubscribe_props {}, + nothrow_awaitable + ); + + std::tie(ec) = co_await client.async_disconnect( + async_mqtt5::disconnect_rc_e::disconnect_with_will_message, + async_mqtt5::disconnect_props {}, + nothrow_awaitable + ); + + co_return; +} + + +int main(int argc, char** argv) { + asio::io_context ioc; + + // Make an instance of mqtt_client. Establish a TCP connection with the Broker. + client_type c(ioc.get_executor(), ""); + + c.credentials("test-client", "username", "password") + .brokers("mqtt.broker", 1883) + .run(); + + asio::co_spawn(ioc.get_executor(), coroutine(c), asio::detached); + // or... + asio::co_spawn(ioc.get_executor(), nothrow_coroutine(c), asio::detached); + + ioc.run(); +} + +//] diff --git a/include/async_mqtt5/error.hpp b/include/async_mqtt5/error.hpp index d730c2c..bd6b9a8 100644 --- a/include/async_mqtt5/error.hpp +++ b/include/async_mqtt5/error.hpp @@ -161,6 +161,12 @@ public: /// Move constructor. reason_code(reason_code&&) = default; + /// Copy assignment operator. + reason_code& operator=(const reason_code&) = default; + + /// Move assignment operator. + reason_code& operator=(reason_code&&) = default; + /** * \brief Indication if the object holds a Reason Code indicating an error. * diff --git a/include/async_mqtt5/impl/write_op.hpp b/include/async_mqtt5/impl/write_op.hpp index e14366e..37e1785 100644 --- a/include/async_mqtt5/impl/write_op.hpp +++ b/include/async_mqtt5/impl/write_op.hpp @@ -11,7 +11,6 @@ namespace async_mqtt5::detail { template class write_op { - struct on_write_locked {}; struct on_write {}; struct on_reconnect {}; diff --git a/include/async_mqtt5/mqtt_client.hpp b/include/async_mqtt5/mqtt_client.hpp index 2bc0bb1..6f39b83 100644 --- a/include/async_mqtt5/mqtt_client.hpp +++ b/include/async_mqtt5/mqtt_client.hpp @@ -133,7 +133,6 @@ public: detail::sentry_op { _svc_ptr }.perform(); } - // TODO: channel cancel /** * \brief Cancel all asynchronous operations. This function has terminal effects. * @@ -218,7 +217,9 @@ public: * \param retain The \ref retain_e flag. * \param props An instance of \__PUBLISH_PROPS\__. * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation depends on the \ref qos_e specified:\n @@ -250,8 +251,9 @@ public: * * \par Error codes * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n + * - `boost::system::errc::errc_t::success` \n * - `boost::asio::error::operation_aborted` \n + * - `boost::asio::error::no_recovery` \n * - \link async_mqtt5::client::error::pid_overrun \endlink * - \link async_mqtt5::client::error::qos_not_supported \endlink * - \link async_mqtt5::client::error::retain_not_available \endlink @@ -300,7 +302,9 @@ public: * \param topics A list of \ref subscribe_topic of interest. * \param props An instance of \__SUBSCRIBE_PROPS\__. * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: @@ -316,7 +320,8 @@ public: * * \par Error codes * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n * - `boost::asio::error::operation_aborted` \n * - \link async_mqtt5::client::error::pid_overrun \endlink * @@ -357,7 +362,9 @@ public: * \param topic A \ref subscribe_topic of interest. * \param props An instance of \__SUBSCRIBE_PROPS\__. * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: @@ -373,7 +380,8 @@ public: * * \par Error codes * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n * - `boost::asio::error::operation_aborted` \n * - \link async_mqtt5::client::error::pid_overrun \endlink * @@ -402,7 +410,9 @@ public: * \param topics List of Topics to unsubscribe from. * \param props An instance of \__UNSUBSCRIBE_PROPS\__. * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: @@ -418,7 +428,8 @@ public: * * \par Error codes * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n * - `boost::asio::error::operation_aborted` \n * - \link async_mqtt5::client::error::pid_overrun \endlink * @@ -458,7 +469,9 @@ public: * \param topic Topic to unsubscribe from. * \param props An instance of \__UNSUBSCRIBE_PROPS\__. * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: @@ -474,7 +487,8 @@ public: * * \par Error codes * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n * - `boost::asio::error::operation_aborted` \n * - \link async_mqtt5::client::error::pid_overrun \endlink * @@ -506,7 +520,9 @@ public: * or there is a pending Application Message. * * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: @@ -547,7 +563,9 @@ public: * the Broker of the reason for disconnection. * \param props An instance of \__DISCONNECT_PROPS\__. * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: @@ -587,7 +605,9 @@ public: * See \ref mqtt_client::cancel. * * \param token Completion token that will be used to produce a - * completion handler, which will be called when the operation completed. + * completion handler. The handler will be invoked when the operation is completed. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. * * \par Handler signature * The handler signature for this operation: