25 Commits

Author SHA1 Message Date
3c8b260430 Make find_package() call optional to allow for boost submodule installations 2025-02-12 16:29:46 +01:00
d52090f438 Add support for g++-13,14
Summary:
related to T13767, #9

fix compilation error on g++-14
add g++-13 & g++-14 workflow to CI (requires ubuntu 24.04)

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29794
2024-06-03 09:57:24 +02:00
927c1c6e3a Update license to BSL-1.0
Summary: related to T13767 T13767

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29686
2024-05-27 10:59:59 +02:00
3570d56d9e Update versions of CI & coverage github actions
Summary:
related to T13767

Upgraded github actions to versions that run on node 20 (v4).
Previous versions run on node 16 and are deprecated.
https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29494
2024-05-16 12:34:14 +02:00
dc2b2d9bbb Do not use temporaries in co_await expressions (g++ compiler bug)
Summary: related to T13767

Reviewers: iljazovic

Reviewed By: iljazovic

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D29449
2024-05-15 08:50:04 +02:00
68840dda95 Add missing topic alias name validation
Summary:
related to T13767

- allow empty topic name + topic alias in props
- add mqtt features tests

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29445
2024-05-15 07:43:44 +02:00
b2338d4135 async_run's associated ex will not replace mqtt_client's default ex
Summary: related to T13767

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29383
2024-05-13 08:56:20 +02:00
794f48e915 Add support for g++-9 compiler
Summary: related to #7, T13767

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29302
2024-05-09 14:55:04 +02:00
73ec4431ff Fix colours in README code box
Summary: related to T13767

Reviewers: iljazovic

Reviewed By: iljazovic

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D29246
2024-05-03 12:15:08 +02:00
27c4ca1535 Add a build with CMake example to README and Introduction page
Summary: related to T13767

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29243
2024-05-03 12:04:07 +02:00
9b7f852342 Update documentation and examples for clarity
Summary:
related to T13767

- Rewrite chaining of configuration functions
- Move examples not related to documentation to dev folder
- Rewrite links for clarity
- Update CMakeLists.txt to run one of the examples
- Rewrite auto reconnect and auto retry mechanism for clarity

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D29044
2024-04-24 14:08:46 +02:00
bfae05f7a9 Remove deprecated API usage
Summary: related to T13767

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28958
2024-04-16 11:54:46 +02:00
ccc8d48efc Fix MSVC warning and test fail.
Reviewers: korina

Reviewed By: korina

Differential Revision: https://repo.mireo.local/D28583
2024-03-22 11:37:22 +01:00
174b0c312f Maintain publish order even when throttling.
Summary: When broker sets receive maximum, and multiple messages with QoS 0 and QoS > 0 are published at the same time, we should maintain order in which async_publish calls were made. Before, QoS > 0 publishes would always be put at the end of the buffer.

Reviewers: ivica

Reviewed By: ivica

Subscribers: korina

Differential Revision: https://repo.mireo.local/D28456
2024-03-22 10:45:18 +01:00
aea5175e8a Add a chapter on multithreading and thread safety
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28563
2024-03-21 14:59:37 +01:00
c7a4fcd507 Add a chapter on asio compliance - allocators
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28543
2024-03-21 08:31:23 +01:00
b545d67e26 Add a chapter on asio compliance - per operation cancellation
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28516
2024-03-20 10:29:13 +01:00
0d6bbbc424 Add a chapter on asio compliance - executors
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28496
2024-03-20 10:11:55 +01:00
8dfbdf2d38 Update publisher and receiver examples
Summary:
related to T12804
publisher example will now showcase how to publish a message every 5 seconds
receiver example will now show how to subscribe, indefinitely receive messages and resubscribe if needed

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28472
2024-03-15 15:23:33 +01:00
4b65ffc194 Add a chapter on disconnection
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28465
2024-03-15 07:39:58 +01:00
bbcc7b1cd0 Add a chapter on optimising communication (multiflight, packet queing)
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28440
2024-03-13 15:00:02 +01:00
5886fbf82b Add a chapter on maintaing a healthy connection (keep alive, ping, read timeout)
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28240
2024-03-12 12:32:17 +01:00
f34705d74e Add a chapter on auto-reconnect and retry mechanism
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28198
2024-02-29 14:19:28 +01:00
a1249b433d Reconnect on every Reason Code > 0x80 received in the CONNACK packet
Summary: related to T13767

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28217
2024-02-29 14:18:33 +01:00
3abacf7a41 Documentation rework - add Configuring the Client chapter
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28182
2024-02-28 10:30:32 +01:00
145 changed files with 3243 additions and 2008 deletions

View File

@ -1,3 +1,10 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: CI
on: [push, pull_request]
@ -8,7 +15,7 @@ env:
jobs:
windows:
name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.build-type }}"
name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }}"
defaults:
run:
shell: cmd
@ -53,7 +60,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup OpenSSL
env:
@ -107,7 +114,7 @@ jobs:
.\\test\\build\\${{ matrix.build-type }}\\mqtt-test.exe
posix:
name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.build-type }}"
name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.container }}"
defaults:
run:
@ -117,6 +124,16 @@ jobs:
fail-fast: false
matrix:
include:
- toolset: g++-9
compiler: g++-9
install: g++-9
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-10
compiler: g++-10
install: g++-10
@ -133,6 +150,26 @@ jobs:
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-13
compiler: g++-13
install: g++-13
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-14
compiler: g++-14
install: g++-14
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 20
cxxflags: ''
ldflags: ''
@ -186,7 +223,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup container environment
if: matrix.container

View File

@ -1,3 +1,10 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: coverage
on: [push, pull_request]
@ -8,7 +15,7 @@ env:
jobs:
posix:
name: "coverage ${{ matrix.compiler }} -std=c++${{ matrix.cxxstd }}"
name: "coverage ${{ matrix.compiler }} -std=c++${{ matrix.cxxstd }} ${{ matrix.container }}"
defaults:
run:
shell: bash
@ -19,7 +26,6 @@ jobs:
include:
- toolset: coverage
compiler: g++-11
install: g++-11
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: 20
@ -33,16 +39,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup container environment
if: matrix.container
run: |
apt-get update
apt-get -y install sudo wget tar cmake openssl libssl-dev pkg-config lcov
apt-get -y install sudo wget tar cmake openssl libssl-dev pkg-config lcov gpg git
- name: Install compiler
run: sudo apt-get install -y ${{ matrix.install }}
run: sudo apt-get install -y ${{ matrix.compiler }}
- name: Setup Boost
run: |
@ -76,9 +82,12 @@ jobs:
lcov --remove coverage.info '**/boost/*' --output-file coverage.info
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
files: coverage.info
verbose: true
file: coverage.info
disable_search: true
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
plugin: noop
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15)
project(async-mqtt5 VERSION 0.0.1 LANGUAGES CXX)
project(async-mqtt5 VERSION 1.0.1 LANGUAGES CXX)
include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)
@ -21,7 +21,9 @@ target_include_directories(
target_compile_features(async_mqtt5 INTERFACE cxx_std_17)
find_package(Boost 1.82 REQUIRED)
if (NOT ASYNC_MQTT5_SKIP_FIND_PACKAGE)
find_package(Boost 1.82 REQUIRED)
endif()
target_link_libraries(async_mqtt5
INTERFACE
Boost::headers

28
LICENSE
View File

@ -1,11 +1,23 @@
Copyright (c) 2001-2023 Mireo, EU
Boost Software License - Version 1.0 - August 17th, 2003
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -5,7 +5,7 @@ Branch | Windows/Linux Build | Coverage | Documentation |
-------|---------------------|----------|---------------|
[`master`](https://github.com/mireo/async-mqtt5/tree/master) | [![build status](https://github.com/mireo/async-mqtt5/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/mireo/async-mqtt5/actions/workflows/ci.yml) | [![codecov](https://codecov.io/gh/mireo/async-mqtt5/branch/master/graph/badge.svg)](https://codecov.io/gh/mireo/async-mqtt5/branch/master) | [Documentation](https://spacetime.mireo.com/async-mqtt5/)
Async.MQTT5 is a professional, industrial-grade C++17 client built on [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio.html). This client is designed for publishing or receiving messages from an MQTT 5.0 compatible broker. Async.MQTT5 represents a comprehensive implementation of the MQTT 5.0 protocol standard, offering full support for publishing or receiving messages with QoS 0, 1, and 2.
Async.MQTT5 is a professional, industrial-grade C++17 client built on [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio.html). This Client is designed for publishing or receiving messages from an MQTT 5.0 compatible Broker. Async.MQTT5 represents a comprehensive implementation of the MQTT 5.0 protocol standard, offering full support for publishing or receiving messages with QoS 0, 1, and 2.
Our clear intention is to include the Async.MQTT5 library into [Boost](https://www.boost.org/). We are actively working on it.
@ -13,9 +13,9 @@ Motivation
---------
The [MQTT](https://mqtt.org/) protocol is widely utilised for communication in various real-world scenarios, primarily serving as a reliable communication protocol for data transfer to and from IoT devices. While the MQTT protocol itself is relatively straightforward, integrating it into an application can be complex, especially due to the challenging implementation of message retransmission after a disconnect/reconnect sequence.
The aim of Async.MQTT5 is to provide a very simple asynchronous C++ interface for application developers. The internal client's implementation manages network and MQTT protocol details. Notably, the client does not expose connect functions (nor asynchronous connect functions); instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the client.
The aim of Async.MQTT5 is to provide a very simple asynchronous C++ interface for application developers. The internal Client's implementation manages network and MQTT protocol details. Notably, the Client does not expose connect functions (nor asynchronous connect functions); instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the Client.
The Async.MQTT5 interface aligns seamlessly with the Boost.Asio asynchronous model. The client's asynchronous functions are compatible with all completion tokens supported by Boost.Asio.
The Async.MQTT5 interface aligns seamlessly with the Boost.Asio asynchronous model. The Client's asynchronous functions are compatible with all completion tokens supported by Boost.Asio.
Features
---------
@ -24,7 +24,7 @@ The library attempts to embody this belief with a range of key features designed
- **Complete TCP, TLS/SSL, and WebSocket support**
- **User-focused simplicity**: Providing an interface that is as simple as possible without compromising functionality.
- **Prioritized efficiency**: Utilising network and memory resources as efficiently as possible.
- **Prioritised efficiency**: Utilising network and memory resources as efficiently as possible.
- **Minimal memory footprint**: Ensuring optimal performance in resource-constrained environments typical of IoT devices.
- **Automatic reconnect**: Automatically attempt to re-establish a connection in the event of a disconnection.
- **Fully Boost.Asio compliant**: The interfaces and implementation strategies are built upon the foundations of Boost.Asio. Boost.Asio and Boost.Beast users will have no issues understanding and integrating Async.MQTT5. Furthermore, Async.MQTT5 integrates well with any other library within the Boost.Asio ecosystem.
@ -66,8 +66,7 @@ The following example illustrates a simple scenario of configuring a Client and
int main() {
boost::asio::io_context ioc;
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
client_type c(ioc);
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.brokers("<your-mqtt-broker>", 1883)
@ -85,7 +84,27 @@ int main() {
ioc.run();
}
```
To see more examples, visit [Examples](https://spacetime.mireo.com/async-mqtt5/async_mqtt5/examples.html).
To see more examples, visit [Examples](https://github.com/mireo/async-mqtt5/tree/master/example).
Building with CMake
---------
You can use the `CMakeLists.txt` provided in our repository to compile and run any of the [examples](https://github.com/mireo/async-mqtt5/tree/master/example) or your own source files.
The following commands demonstrate compiling and running the previous code using CMake on Linux.
The source file is located at [example/hello_world_over_tcp.cpp](https://github.com/mireo/async-mqtt5/blob/master/example/hello_world_over_tcp.cpp).
```bash
# navigate to the root folder of Async.MQTT5
# compile the example
cmake -S . -B {build-folder} -DBUILD_EXAMPLES=ON -DCMAKE_EXE_LINKER_FLAGS="-pthread"
cmake --build {build-folder}
# run the example
./{build-folder}/example/example
```
You can edit the [example/CMakeLists.txt](https://github.com/mireo/async-mqtt5/blob/master/example/CMakeLists.txt) file to compile the source file of your choice.
By default, it will compile [example/hello_world_over_tcp.cpp](https://github.com/mireo/async-mqtt5/blob/master/example/hello_world_over_tcp.cpp).
When to use
---------
@ -94,11 +113,11 @@ When to use
- Your application uses Boost.Asio and requires integrating a MQTT Client.
- You require asynchronous access to an MQTT Broker.
- You are developing a higher-level component that requires a connection to an MQTT Broker.
- You require a dependable and resilient MQTT Client that can automatically manage all network-related issues.
- You require a dependable and resilient MQTT Client to manage all network-related issues automatically.
It may not be suitable for you if:
- You solely require synchronous access to an MQTT Broker.
- The MQTT Broker you are connecting to does not support the MQTT 5 version.
- The MQTT Broker you connect to does not support the MQTT 5 version.
Requirements
@ -109,8 +128,8 @@ Async.MQTT5 is a header-only library. To use Async.MQTT5 it requires the followi
- **OpenSSL**. Only if you require an SSL connection by using [boost::asio::ssl::stream](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/reference/ssl__stream.html).
Async.MQTT5 has been tested with the following compilers:
- clang 12.0, 13.0, 14.0, 15.0 (Linux)
- GCC 10, 11, 12 (Linux)
- clang 12.0 - 15.0 (Linux)
- GCC 9 - 14 (Linux)
- MSVC 14.37 - Visual Studio 2022 (Windows)
Contributing
@ -119,25 +138,6 @@ When contributing to this repository, please first discuss the change you wish t
You may merge a Pull Request once you have the sign-off from other developers, or you may request the reviewer to merge it for you.
License
---------
Copyright (c) 2001-2024 Mireo, EU
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Credits
---------

View File

@ -1,6 +1,6 @@
if(PROJECT_IS_TOP_LEVEL)
set(
CMAKE_INSTALL_INCLUDEDIR "include/async-mqtt5-${PROJECT_VERSION}"
CMAKE_INSTALL_INCLUDEDIR "/async-mqtt5-${PROJECT_VERSION}"
CACHE STRING ""
)
set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH)

View File

@ -1,3 +1,10 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
codecov:
require_ci_to_pass: true
@ -19,5 +26,9 @@ coverage:
project:
default:
informational: true
github_checks:
annotations: true
fixes:
- ".*/async_mqtt5/::include/async_mqtt5/"

View File

@ -1,3 +1,10 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
project async_mqtt5/doc ;
import os ;
@ -32,6 +39,7 @@ make xml/index.xml
# additional dependencies
../include/async_mqtt5/error.hpp
../include/async_mqtt5/reason_codes.hpp
../include/async_mqtt5/types.hpp
../include/async_mqtt5/mqtt_client.hpp
:
@ -129,7 +137,8 @@ boostbook async_mqtt5
<xsl:param>chapter.autolabel=1
<xsl:param>chunk.section.depth=8
<xsl:param>chunk.first.sections=1
<xsl:param>generate.toc=""
<xsl:param>toc.max.depth=2
<xsl:param>generate.toc="chapter toc,title section nop reference nop part toc"
<xsl:param>html.stylesheet=boostbook.css
:
<dependency>stylesheets

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[library Async.MQTT5: a C++17 MQTT client
[quickbook 1.7]
[copyright 2023-2024 Mireo]
@ -20,6 +26,7 @@
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
[template ghreflink[path text] [@https://github.com/mireo/async-mqtt5/blob/master/[path] [text]]]
[template reflink2[id text][link async_mqtt5.ref.[id] [^[text]]]]
[template reflink[id][reflink2 [id] [id]]]
[template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]]
@ -41,6 +48,14 @@
[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]]
[def __Beast__ [@boost:/libs/beast/index.html Boost.Beast]]
[def __ASIO_PER_OP_CANCELLATION__ [@boost:/doc/html/boost_asio/overview/core/cancellation.html Per-Operation Cancellation]]
[def __ASIO_ALLOCATORS__ [@boost:/doc/html/boost_asio/overview/model/allocators.html Allocators]]
[def __ASIO_CUSTOM_MEMORY_ALLOCATION__ [@boost:/doc/html/boost_asio/overview/core/allocation.html Custom Memory Allocation]]
[def __ASIO_PARALLEL_GROUP__ [@boost:/doc/html/boost_asio/overview/composition/parallel_group.html Co-ordinating Parallel Operations]]
[def __ASIO_STRANDS__ [@boost:/doc/html/boost_asio/overview/core/strands.html Strands: Use Threads Without Explicit Locking]]
[def __IOC__ [@boost:doc/html/boost_asio/reference/io_context.html `boost::asio::io_context`]]
[def __IOC_RUN__ [@boost:doc/html/boost_asio/reference/io_context/run.html `boost::asio::io_context::run()`]]
[def __STRAND__ [@boost:doc/html/boost_asio/reference/io_context__strand.html `boost::asio::io_context::strand`]]
[def __DISPATCH__ [@boost:doc/html/boost_asio/reference/dispatch.html `boost::asio::dispatch`]]
[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`]]
@ -51,6 +66,7 @@
[def __SSL_CONTEXT__ [asioreflink ssl__context ssl::context]]
[def __SSL_STREAM__ [asioreflink ssl__stream ssl::stream<__TCP_SOCKET__>]]
[def __WEBSOCKET_STREAM__ [beastreflink boost__beast__websocket__stream websocket::stream<NextLayer>]]
[def __BEAST_ASYNC_TEARDOWN__ [beastreflink boost__beast__websocket__async_teardown boost::beast::websocket::async_teardown]]
[/ MQTT ]
[def __MQTT__ [@https://mqtt.org/ MQTT]]
@ -108,26 +124,22 @@
[def __REASON_CODES__ [reflink2 Reason_codes `Reason Codes`]]
[def __ERROR_HANDLING__ [reflink2 Error_handling `Error handling`]]
[def __EXAMPLE_CALLBACK__ [link async_mqtt5.examples.callbacks Async functions with callbacks]]
[def __EXAMPLE_COROUTINE__ [link async_mqtt5.examples.cpp20_coroutines Async functions with C++20 coroutines]]
[def __EXAMPLE_FUTURE__ [link async_mqtt5.examples.futures Async functions with futures]]
[include 01_intro.qbk]
[include 02_examples.qbk]
[include 02_configuring_the_client.qbk]
[include 03_auto_reconnect.qbk]
[include 04_maintaining_a_stable_connection.qbk]
[include 05_optimising_communication.qbk]
[include 06_disconnecting_the_client.qbk]
[include 07_asio_compliance.qbk]
[include 11_multithreading.qbk]
[include 15_examples.qbk]
[include examples/Examples.qbk]
[section:ref Reference]
[xinclude reference/quickref.xml]
[block'''<part label="Two: Reference">''']
[include reference/reference.qbk]
[h3 Table of Contents]
* [link async_mqtt5.intro Introduction]
* [link async_mqtt5.examples Examples]
* [link async_mqtt5.examples.publisher The publisher]
* [link async_mqtt5.examples.receiver The receiver]
* [link async_mqtt5.examples.network_connection Establishing a network connection with different protocols]
* [link async_mqtt5.examples.network_connection.tcp_ip_connection TCP/IP connection]
* [link async_mqtt5.examples.network_connection.tls_ssl_connection TLS/SSL connection]
* [link async_mqtt5.examples.network_connection.websocket_connection WebSocket connection]
* [link async_mqtt5.examples.completion_tokens Completion tokens]
* [link async_mqtt5.examples.callbacks Async functions with callbacks]
* [link async_mqtt5.examples.cpp20_coroutines Async functions with C++20 coroutines]
* [link async_mqtt5.examples.futures Async functions with futures]
* [link async_mqtt5.ref Reference]
[block'''</part>''']
[endsect]

View File

@ -1,8 +1,14 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:intro Introduction]
[nochunk]
__Self__ is a C++17 client built on __Asio__.
This client is designed for publishing or receiving messages from an MQTT 5.0 compatible broker.
This client is designed for publishing or receiving messages from an MQTT 5.0 compatible Broker.
__Self__ represents a comprehensive implementation of the MQTT 5.0 protocol standard,
offering full support for publishing or receiving messages with QoS 0, 1, and 2.
@ -13,12 +19,12 @@ While the MQTT protocol itself is relatively straightforward, integrating it int
especially due to the challenging implementation of message retransmission after a disconnect/reconnect sequence.
The aim of __Self__ is to provide a very simple asynchronous C++ interface for application developers.
The internal client's implementation manages network and MQTT protocol details.
Notably, the client does not expose connect functions (nor asynchronous connect functions);
instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the client.
The internal Client's implementation manages network and MQTT protocol details.
Notably, the Client does not expose connect functions (nor asynchronous connect functions);
instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the Client.
The __Self__ interface aligns seamlessly with the __Asio__ asynchronous model.
The client's asynchronous functions are compatible with all completion tokens supported by __Asio__.
The Client's asynchronous functions are compatible with all completion tokens supported by __Asio__.
[heading Features]
__Self__ is a library designed with the core belief that users should focus solely on their application logic, not the network complexities. [br]
@ -26,7 +32,7 @@ The library attempts to embody this belief with a range of key features designed
* [*Complete TCP, TLS/SLL, and WebSocket support]
* [*User-focused simplicity]: Providing an interface that is as simple as possible without compromising functionality.
* [*Prioritized efficiency]: Utilising network and memory resources as efficiently as possible.
* [*Prioritised efficiency]: Utilising network and memory resources as efficiently as possible.
* [*Minimal memory footprint]: Ensuring optimal performance in resource-constrained environments typical of IoT devices.
* [*Automatic reconnect]: Automatically attempt to re-establish a connection in the event of a disconnection.
* [*Fully Boost.Asio compliant]: The interfaces and implementation strategies are built upon the foundations of __Asio__. [br]
@ -44,25 +50,26 @@ In the event of a connection failure with one Broker, the Client switches to the
* [*Offline buffering]: While offline, it automatically buffers all the packets to send when the connection is re-established.
[heading Example]
The following example illustrates a simple scenario of configuring a Client and publishing an Application Message.
The following example illustrates a simple scenario of configuring a Client and publishing a
"Hello World!" Application Message with `QoS` 0.
[!c++]
#include <iostream>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
client_type c(ioc);
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.brokers("<your-mqtt-broker>", 1883)
.async_run(asio::detached);
.async_run(boost::asio::detached);
c.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
@ -78,6 +85,23 @@ The following example illustrates a simple scenario of configuring a Client and
To see more examples, visit [link async_mqtt5.examples Examples].
[heading Building with CMake]
You can use the `CMakeLists.txt` provided in our repository to compile and run any of the [link async_mqtt5.examples examples] or your own source files.
The following commands demonstrate compiling and running the previous code using CMake on Linux.
The source file is located at [ghreflink example/hello_world_over_tcp.cpp example/hello_world_over_tcp.cpp].
# navigate to the root folder of Async.MQTT5
# compile the example
cmake -S . -B {build-folder} -DBUILD_EXAMPLES=ON -DCMAKE_EXE_LINKER_FLAGS="-pthread"
cmake --build {build-folder}
# run the example
./{build-folder}/example/example
You can edit the [ghreflink example/CMakeLists.txt example/CMakeLists.txt] file to compile the source file of your choice.
By default, it will compile [ghreflink example/hello_world_over_tcp.cpp example/hello_world_over_tcp.cpp].
[heading When to use]
__Self__ might be suitable for you if any of the following statements is true:
@ -85,12 +109,12 @@ __Self__ might be suitable for you if any of the following statements is true:
* Your application uses __Asio__ and requires integrating a MQTT Client.
* You require asynchronous access to an MQTT Broker.
* You are developing a higher-level component that requires a connection to an MQTT Broker.
* You require a dependable and resilient MQTT Client that can automatically manage all network-related issues.
* You require a dependable and resilient MQTT Client to manage all network-related issues automatically.
It may not be suitable for you if:
* You solely require synchronous access to an MQTT Broker.
* The MQTT Broker you are connecting to does not support the MQTT 5 version.
* The MQTT Broker you connect to does not support the MQTT 5 version.
[heading Requirements]
@ -103,8 +127,8 @@ To use __Self__ it requires the following:
__Self__ has been tested with the following compilers:
* clang 12.0, 13.0, 14.0, 15.0 (Linux)
* GCC 10, 11, 12 (Linux)
* clang 12.0 - 15.0 (Linux)
* GCC 9 - 14 (Linux)
* MSVC 14.37 - Visual Studio 2022 (Windows)
[heading Acknowledgements]

View File

@ -0,0 +1,90 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:configuring_the_client Configuring the Client]
[nochunk]
This section guides you through the steps to properly configure the __Client__ to establish a connection with your chosen MQTT Broker.
The __Client__ does *not* expose connect functions (nor asynchronous connect functions).
Instead, it features a function that will start the Client (see [refmem mqtt_client async_run]).
All configuration of the __Client__ must be finalised before invoking [refmem mqtt_client async_run].
Upon calling this function, the __Client__ makes its initial attempt to connect.
[section:transport_protocol Choosing a suitable transport protocol]
The initial step is selecting an appropriate transport protocol for your MQTT connection.
The __MQTT__ protocol relies on an underlying transport protocol that guarantees the orderly and reliable
transmission of byte streams between the Client and Server in both directions.
Common transport protocols meeting these criteria include TCP/IP, TLS/SSL for secure transmissions,
and WebSocket over TCP/IP and TLS/SSL.
MQTT Brokers come in various implementations and support different transport protocols.
Therefore, it is important to familiarise yourself with the protocols your chosen Broker supports.
Additionally, gather any necessary details such as certificate authority, client certificate, or private keys,
which might be required for establishing a secure connection.
The upcoming examples will demonstrate the configuration and the publishing of a "Hello World!" application message at `QoS` 0,
using various transport protocols:
* [link async_mqtt5.hello_world_over_tcp hello_world_over_tcp.cpp]
* [link async_mqtt5.hello_world_over_tls hello_world_over_tls.cpp]
* [link async_mqtt5.hello_world_over_websocket_tcp hello_world_over_websocket_tcp.cpp]
* [link async_mqtt5.hello_world_over_websocket_tls hello_world_over_websocket_tls.cpp]
[endsect] [/transport_protocol]
[section:customisation Customising your MQTT connection]
The __Client__ offers a variety of functions for customising your MQTT connection settings.
These functionalities include:
* *Specifying a list of Brokers:* You *must* use this function to assign a list of Brokers that the __Client__ will try to connect to (see [refmem mqtt_client brokers]).
The __Client__ allows for the specification of multiple Brokers for the connections.
It is important to ensure that *only Brokers belonging to the same cluster are included in this list*.
Listing Brokers from different clusters may lead to inconsistencies between MQTT Sessions.
* *Setting connection Credentials:* Set the authentication details (Client Identifier, User Name, Password) (see [refmem mqtt_client credentials]).
* *Configuring Keep Alive Interval:* Set the maximum allowed interval between two consecutive transmissions from the Client (see [refmem mqtt_client keep_alive]).
* *Assign a custom user-implemented authenticator:* The custom authentication will be used for __ENHANCED_AUTH__ ([refmem mqtt_client authenticator]).
* *Defining CONNECT Packet Properties:* Specify properties that will be included in the __CONNECT__ packet sent during connection initiation (see [refmem mqtt_client connect_property] and [refmem mqtt_client connect_properties]).
It is important to note that these configurations apply to every Broker listed in [refmem mqtt_client brokers].
To modify any configuration parameters, you must first stop the __Client__ using [refmem mqtt_client cancel] or [refmem mqtt_client async_disconnect].
Afterwards, you can re-apply the configurations and restart the __Client__ with [refmem mqtt_client async_run].
[endsect] [/customisation]
[section:establishing_a_connection Establishing a connection]
The __Client__ initiates a connection with the first Broker from the list of Brokers assigned using [refmem mqtt_client brokers].
A single attempt at connecting involves several steps:
# Resolving the Broker's hostname.
# Creating a TCP connection.
# Performing the TLS/SSL Handshake /(if applicable)/.
# Performing the WebSocket Handshake /(if applicable)/.
# Performing the MQTT handshake. This involves sending the __CONNECT__ packet to the Broker, /(if applicable)/ exchanging __AUTH__ packets (see __ENHANCED_AUTH__), and continuing until the Broker sends the __CONNACK__ packet.
The connection is successfully established once the __Client__ receives a __CONNACK__ packet with Reason Code `0x00` (`Success`).
The attempt fails if any of the following occurs:
* Any of the steps from 1 to 5 encounters a failure.
* The __Client__ does not receive the results of the hostname resolution within `5 seconds`.
* The __Client__ is unable to complete steps 2 through 5 within `5 seconds`.
If this connection attempt is unsuccessful, it proceeds to the next Broker in order, continuing this process until it establishes a connection or exhausts all options without success.
Upon reaching the end of the list without a successful connection, the __Client__ enters a backoff period before making another round of attempts with the entire list.
The duration of this backoff period in milliseconds is determined by the formula:
2^(min(retry_count, 4)) * 1000 + jitter
In this formula, `retry_count` represents the number of complete cycles through the list without a successful connection,
and `jitter` is a randomly chosen value between `-500ms` to `500ms`, intended to prevent synchronised reconnection attempts in scenarios with multiple clients.
[endsect] [/establishing_a_connection]
[endsect] [/configuring_the_client]

View File

@ -1,26 +0,0 @@
[section:examples Examples]
The main class in __Self__ is __Client__, and the upcoming examples will briefly explain how to use it.
The first examples will show two common uses of an MQTT client: as the publisher and as the receiver.
* [link async_mqtt5.examples.publisher The publisher]
* [link async_mqtt5.examples.receiver The receiver]
The following section will show how to use different underlying transport protocols (such as TCP, SSL and WebSocket)
to establish a connection to a MQTT Broker.
* [link async_mqtt5.examples.network_connection Establishing a network connection with different protocols]
The final section will showcase how to use asynchronous functions in __Client__
with different __CompletionToken__.
* [link async_mqtt5.examples.completion_tokens Completion tokens]
* [link async_mqtt5.examples.callbacks Async functions with callbacks]
* [link async_mqtt5.examples.cpp20_coroutines Async functions with C++20 coroutines]
* [link async_mqtt5.examples.futures Async functions with futures]
[include examples/Tutorial.qbk]
[include examples/Network_connection.qbk]
[include examples/Completion_tokens.qbk]
[endsect]

View File

@ -0,0 +1,107 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:auto_reconnect Built-in auto-reconnect and retry mechanism]
[nochunk]
The auto-reconnect and retry mechanism is a key feature of the __Self__ library.
It is designed to internally manage the complexities of disconnects, backoffs, reconnections, and message retransmissions.
These tasks, if handled manually, could lead to extended development times, difficult testing, and compromised reliability.
By automating these processes, the __Self__ library enables users of the __Client__ to focus primarily
on the application's functionality without worrying about the repercussions of lost network connectivity.
You can call any asynchronous function within the __Client__ regardless of its current connection status.
If the __Client__ is offline, it will queue all outgoing requests and transmit them as soon as the connection is restored.
In situations where the connection is unexpectedly lost mid-protocol flow,
the __Client__ complies with the MQTT protocol's specified message delivery retry mechanisms.
The following example will showcase how the __Client__ internally manages various scenarios, including successful transmission, offline queuing,
and connection loss retransmissions, when executing a request to publish a message with QoS 1.
Note that the same principle applies to all other asynchronous functions within the __Client__
(see /Completion condition/ under [refmem mqtt_client async_publish], [refmem mqtt_client async_subscribe], [refmem mqtt_client async_unsubscribe],
and [refmem mqtt_client async_disconnect]).
// Publishing with QoS 1 involves a two-step process: sending a PUBLISH message to the Broker and awaiting a PUBACK (acknowledgement) response.
// The scenarios that might happen are:
// 1) The Client will immediately send the PUBLISH message and start to wait for the PUBACK reply.
// The user's completion handler will not be invoked yet, regardless of whether the Client successfully sent the message.
// 2) When the Client fails to send the message, it will try to reconnect to the Broker automatically.
// If it succeeds, it will resend the message. If reconnect fails, the Client will try to connect again until it succeeds.
// Meanwhile, the user's completion handler will NOT be invoked except when the Client detects an unrecoverable error (for example, when the list of configured brokers is empty).
// Note that the Client will try to connect indefinitely, meaning the user's completion handler may never be invoked.
// 3) When the Client successfully sends the PUBLISH message, the Broker is required to respond with a PUBACK message.
// The reply message may indicate success or an MQTT error (for example, signalling that the message is not accepted due to implementation or administrative limits).
// The Client will read the PUBACK message and call the user's completion handler with the appropriate arguments.
// 4) The PUBACK message should arrive within 20 seconds of the PUBLISH message being successfully sent.
// If it does not, the PUBLISH message will be sent again. In the meantime, the user's callback will not be invoked.
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"my-topic", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::puback_props props) {
// This callback is invoked under any of the following circumstances:
// a) The Client successfully sends the PUBLISH packet and receives a PUBACK from the Broker.
// b) The Client encounters a non-recoverable error, such as a cancellation or providing invalid parameters
// to async_publish, which prevents the message from being sent.
}
);
[section:sentry ]
[endsect] [/sentry]
[section:cons Considerations and limitations]
The integrated auto-reconnect and retry mechanism greatly improves the user experience
by simplifying complex processes and ensuring continuous connections.
However, it is important to be mindful of certain limitations and considerations associated with this feature.
[heading Delayed handler invocation]
During extended periods of __Client__ downtime, the completion handlers for asynchronous functions,
such as those used in [refmem mqtt_client async_publish], may face considerable delays before invocation.
This can result in users being left in the dark regarding the status of their requests due to the absence of prompt feedback on the initiated actions.
[heading Concealing configuration-related issues]
The __Client__ will always try to reconnect to the Broker(s) regardless of the reason why the connection was previously closed.
This is desirable behaviour when the connection gets dropped due to underlying stream transport issues,
such as when a device connected to the network loses its GSM connectivity.
However, the connection may be closed (or never established) for other reasons,
which are typically related to misconfiguration of broker IPs, ports, expired or incorrect TLS, or MQTT-related errors,
such as trying to communicate with a Broker that does not support MQTT 5.
In these cases, the __Client__ will still endlessly try to connect to Broker(s), but the connection will never succeed.
The most challenging problem here is that users of the __Client__ do not get informed in any way that the connection cannot be established.
So, if you make a typo in the Broker's IP, run the __Client__, and publish some message, the [refmem mqtt_client async_publish] callback will never be invoked,
and you will not "catch" the error or detect the root cause of the issue.
The possible alternative approach, where the __Client__ would return something like "unrecoverable error"
when you try to publish a message to a misconfigured Broker, would have a terrible consequence if the Broker itself is misconfigured.
For example, suppose someone forgets to renew the TLS certificate on the Broker.
The connection will be broken in that case, and the __Client__ would report an "unrecoverable error" through the [refmem mqtt_client async_publish] method.
Now, the expired TLS certificate on the Broker is most probably a temporary issue,
so it is natural that the __Client__ would try to reconnect until the certificate gets renewed.
But, if the __Client__ stops retrying when it detects such an "unrecoverable error," then the decision of when to reconnect would be left to the user.
By design, one of the main functional requirements of the __Client__ was to handle reconnection steps automatically and correctly.
If the decision for reconnection were left to the user, then the user would need to handle all those error states manually,
which would dramatically increase the complexity of the user's code, not to mention how difficult it would be to cover all possible error states.
The proposed approach for detecting configuration errors in the __Client__ is to use some simple logging facility during development.
Log lines should be injected directly into the __Client__ code (typically in the connect_op.hpp file), and logs would uncover misconfigurations (if any).
[heading Increased resource consumption]
The __Client__ is designed to automatically buffer requests that are initiated while it is offline.
During extended downtime or when a high volume of requests accumulates, this can lead to an increase in memory usage.
This aspect is significant for devices with limited resources, as the growing memory consumption can impact their performance and functionality.
[endsect] [/cons]
[endsect] [/auto_reconnect]

View File

@ -0,0 +1,90 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:connection_maintenance Maintaining a stable connection]
[nochunk]
This chapter delves into the strategies used by the __Client__ to sustain a stable connection with the Broker.
[section:keep_alive Understanding Keep Alive and Server Keep Alive]
Firstly, it is important to understand `Keep Alive` and `Server Keep Alive` and their role
in maintaining a stable connection.
The MQTT protocol defines a `Keep Alive` value, which is the maximum time interval that is permitted to elapse
between the point at which the Client finishes transmitting one packet and the point at which it starts sending the next.
In some instances, the Broker might propose a `Server Keep Alive` interval,
which the Client should adopt instead of the previously set `Keep Alive` interval (further referred to as the negotiated `Keep Alive` interval).
If the Client does not transmit any packet within a period equal to `1.5` times the negotiated `Keep Alive` interval,
the Broker is required to close the connection to the Client.
The Client is responsible for ensuring that the interval between packets being sent does not exceed the negotiated `Keep Alive` interval.
The __Client__ does this automatically by sending a __PINGREQ__ to the Broker every negotiated `Keep Alive` seconds,
independent of any ongoing activity in the connection.
If the negotiated `Keep Alive` interval is `0`, the Keep Alive mechanism is deactivated, and the __Client__ will *not* send any __PINGREQ__
packets. This makes the user responsible for maintaining the connection with the Broker.
Use [refmem mqtt_client keep_alive] function during the __Client__ configuration phase to specify your preferred `Keep Alive` time in seconds.
Otherwise, the __Client__ defaults to a `Keep Alive` period of `60 seconds`.
[note
The MQTT does not use the TCP's built-in keep-alive mechanism as it is inflexible and limited in that it can be configured solely at the operating system level.
The default time a connection must be idle before sending the first keep-alive packet is typically set to 2 hours,
exceeding the tolerances of most MQTT applications.
]
[endsect] [/keep_alive]
[section:half_open_connections Detecting and handling half-open connections]
Ensuring the integrity and stability of network connections is a complex task,
given the number of potential disruptions such as software anomalies,
hardware failures, signal loss, router complications, interruptions in the connectivity path, and more.
A robust Client implementation must accurately detect and resolve such disruptions despite the inherent difficulty in identifying them.
Under normal circumstances, when one side wants to close the TCP connection, a TCP termination procedure, or a 4-way handshake, is initiated.
The initiating side sends a TCP packet indicating the end of data transmission to finish a TCP connection, which is then acknowledged by the receiver.
The receiver, in turn, sends the same termination packet, receiving an acknowledgement from the initiating side to conclude the process.
This ensures both sides have finished sending and receiving all data and agree to close the connection gracefully.
However, the connection may be abruptly terminated due to external disturbances.
For example, consider a situation in which the Client communicates with a Broker but suddenly experiences an unexpected signal loss.
Considering neither side initiated a 4-way handshake to close the connection,
the Client has no reason to perceive the connection as anything but open.
Consequently, it will continue to send packets to the Broker and await responses,
oblivious that the packets would never reach the Broker on the other end.
This results in a half-open TCP connection scenario,
where the connection is effectively terminated on one end but remains active on the other.
Without any mechanisms in place to identify half-open connections, the Client risks
remaining in a half-open state indefinitely.
A common strategy to address a half-open state is to incorporate a timeout mechanism.
Within this strategy, the Client anticipates receiving some data from the Broker within a predefined
timeout interval.
Successful data reception within this interval affirms that the connection is active and stable.
However, if no data is received within the timeout interval, the Client will close the connection, presuming network failure.
Consequently, if the Client is in the half-open state, then no data will come through to it, and the connection will time out.
This condition triggers the Client to initiate the reconnection procedure to try to restore the severed connection.
To avoid false detections of a half-open state in idle connections, the Client must ensure consistent data exchange.
This is achieved through periodic __PINGREQ__ packets sent by the Client to the Broker,
which responds with __PINGRESP__ packet, affirming its operational status.
The __Client__ will dispatch a __PINGREQ__ at an interval equal to the negotiated `Keep Alive` seconds and expects
to receive some data
[footnote The __Client__ does not require to specifically receive __PINGRESP__ to its __PINGREQ__. Any data from the Broker will suffice to confirm its status.]
from the Broker within `1.5` times the negotiated `Keep Alive` seconds.
If no data is received within this time, the __Client__ will assume a half-open state and initiate a reconnect procedure
described in the [link async_mqtt5.auto_reconnect Built-in auto-reconnect and retry mechanism].
[important If the negotiated `Keep Alive` value is set to `0`, the timeout mechanism to detect half-open connection
is disabled. As a result, the __Client__ loses its capability to identify and adequately respond to half-open scenarios.]
[endsect] [/half_open_connections]
[endsect] [/connection_maintenance]

View File

@ -0,0 +1,82 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:optimising_communication Optimising communication]
[nochunk]
This chapter provides a detailed breakdown of how __Client__ optimises its communications with the Broker
with multiflight mode for simultanious message handling and strategies for efficient bandwidth usage.
These techniques are key to getting the most out of MQTT in scenarios demanding fast and
dependable message delivery, all while meeting the protocol's quality of service requirements and network efficiency standards.
[section:multiflight The multiflight mode]
The __Self__ library introduces a multiflight feature.
This allows the initiation of multiple asynchronous requests simultaneously, without waiting for the completion of the previous requests.
With this feature, you can repeatedly call [refmem mqtt_client async_publish] or any similar `async_xxx` function
without waiting for the handler invocation of the previous `async_xxx` calls.
This feature is particulary helpful when using __Client__ with callbacks,
as it allows you to quickly dispatch multiple requests one after the other,
instead of nesting them in callbacks.
Consider the example below, [refmem mqtt_client async_publish] with QoS 2 is called `5` times in a `for` loop.
QoS level 2 ensures that each message is delivered exactly once and involves a four-step communication process:
sending a __PUBLISH__ packet, receiving a __PUBREC__ acknowledgement from the Broker,
transmitting a __PUBREL__ packet, and finally receiving a __PUBCOMP__ packet, confirming successful message delivery.
Despite the complexity of initiating several such message exchange sequences consecutively,
the __Client__ will manage all intermediate packet exchange between the __Client__ and the Broker correctly and complete the message delivery.
It is important to note that there is no guarantee that the final handlers will be invoked
in the same order as the corresponding `async_xxx` calls were initiated.
[import ../../example/multiflight_client.cpp]
Source: [link async_mqtt5.multiflight_client multiflight_client.cpp]
[multiflight_client]
[endsect] [/multiflight]
[section:packet_queuing Efficient bandwidth usage with packet queuing]
The __Client__ employs a strategic queuing mechanism crucial in optimising network usage and performance for the user's requests.
This mechanism bundles multiple MQTT packets for transmission within a single TCP packet whenever feasible
[footnote Requests are queued and bundled whenever the __Client__ is in progress of writing previous request(s) to the transport.].
This significantly reduces performance overhead, enhances data output, and reduces the latency associated with individual packet transmissions.
This results in fast performance and efficient use of network resources.
Additionally, the queuing mechanism ensures that __Client__ complies with the `Receive Maximum` value set by the Broker.
This value is used to implement a send quota to restrict the number of __PUBLISH__ packets with QoS > 0 that have not received an acknowledgement
(__PUBACK__ for QoS 1 and __PUBCOMP__ for QoS 2) (see [mqttlink 3901251 `Flow Control`]).
When [refmem mqtt_client async_publish] with QoS > 0 is invoked,
__Client__ evaluates the current count of unacknowledged __PUBLISH__ packets against the Broker's `Receive Maximum` threshold.
If the count is below this threshold, __Client__ dispatches the __PUBLISH__ packet.
Otherwise, it remains in the queue until the count decreases below the threshold.
As a result, in the [link async_mqtt5.multiflight_client multiflight_client.cpp] example,
the __Client__ will transmit all `5` __PUBLISH__ packets in a single TCP packet
if possible [footnote The Broker's `Receive Maximum` is equal to or greater than `5`.].
[endsect] [/packet_queuing]
[section:packet_ordering Packet ordering]
The __Client__ uses a packet ordering mechanism to manage the queued packets pending dispatch to the Broker.
The most important ordering rules are:
- The __PUBLISH__ packets are transmitted in the order they were initiated through [refmem mqtt_client async_publish] calls.
This sequential integrity is preserved even in instances requiring packet retransmission, ensuring consistency in message delivery order.
However, it is important to note that sequentiality is not preserved between QoS 0 and QoS > 0 packets
when the Broker sets up the `Receive Maximum` value.
A Broker can set this value to limit the number of simultaneous QoS > 0 messages they can process,
potentially causing QoS 0 messages to be transmitted ahead of QoS > 0 messages in the delivery order.
- The __DISCONNECT__ packet is sent *in a single TCP packet before any other packets* in the queue.
See [link async_mqtt5.disconnecting_the_client Disconnecting the client] for more information about disconnecting.
[endsect] [/packet_ordering]
[endsect] [/optimising_communication]

View File

@ -0,0 +1,125 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:disconnecting_the_client Disconnecting the Client]
[nochunk]
The __Client__ remains active until it is either destroyed or explicitly stopped.
In idle mode, the __Client__ periodically sends __PINGREQ__ to the Broker to maintain a stable connection.
The proper way to stop the __Client__ is by calling either [refmem mqtt_client cancel] or [refmem mqtt_client async_disconnect].
Invoking [refmem mqtt_client cancel] results in the __Client__ closing the connection to the Broker and cancelling all outstanding
asynchronous operations.
On the other hand, [refmem mqtt_client async_disconnect] will first attempt to send a __DISCONNECT__ packet
[footnote The __Client__ will attempt to send the __DISCONNECT__ packet for `5 seconds`. Regardless of the outcome, the connection will be closed.]
to the Broker in order to notify it about the reason for disconnection,
then close the connection and cancel all outstanding asynchronous operations (equal effect as [refmem mqtt_client cancel]).
[important Regardless of the method used to stop the __Client__, it is recommended to ensure that all the previous asynchronous operations are
completed. Otherwise, they *will be cancelled*.]
Invoking [refmem mqtt_client cancel] or [refmem mqtt_client async_disconnect] will result in a clean and graceful shutdown process.
This ensures that all resources are properly released and all asynchronous operations are
completed [footnote All outstanding operations will complete with error code `boost::asio::error::operation_aborted`.].
Consequently, the execution context (__IOC__) will stop due to a lack of work.
[note The __Client__'s destructor will also call [refmem mqtt_client cancel]. ]
The following code snippet will showcase a scenario of disconnecting the __Client__ and its interaction with other
asynchronous operations.
[heading Example: immediate disconnection and its impact on outstanding asynchronous operations]
The following code snippet is an example of publishing a "Hello World!" message to the Broker with QoS `0`,
followed by the request to disconnect the __Client__.
```
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
}
);
client.async_disconnect(boost::asio::detached);
ioc.run();
}
```
Suppose the Broker is available and the __Client__ can successfully connect to it, then the following
order of events will unfold:
# The Client will successfully establish a connection to the Broker.
# The Client will send a __DISCONNECT__ packet with Reason Code `0x00` (`Normal Disconnection`).
It is important to note that the __PUBLISH__ packet containing the "Hello World!" message will not be transmitted.
As outlined in the `Packet Ordering` in [link async_mqtt5.optimising_communication Optimising communication] section,
[refmem mqtt_client async_publish] and [refmem mqtt_client async_disconnect] will place their corresponding
packets in the queue. However, __DISCONNECT__ packets are prioritised and sent exclusively, ahead of other queued packets.
Therefore, the connection will terminate immediately.
If the __Client__ cannot establish a connection to the Broker,
it will be stopped after `5 seconds`, which is the amount of time the it will spend
trying to send the __DISCONNECT__ packet to the Broker before quitting.
This timeout mechanism ensures that the __Client__ does not indefinitely wait to disconnect,
preserving resources and maintaining efficient operation.
In this case, the proper way to disconnect would be to call [refmem mqtt_client async_disconnect] after the
[refmem mqtt_client async_publish] has been completed.
```
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
```
[section:reusing_the_client Restarting the Client after disconnection]
Once the __Client__ has been successfully stopped, reactivating it is straightforward and requires invoking [refmem mqtt_client async_run].
This method can be called right after initiating [refmem mqtt_client async_disconnect], without waiting for it to complete.
The __Client__ is configurable again in the interval between stopping and restarting.
See `Customising your MQTT connection` in [link async_mqtt5.configuring_the_client Configuring the Client]
for more information.
```
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
client.async_disconnect(boost::asio::detached);
// The Client can be reconfigured again.
client.connect_property(async_mqtt5::prop::session_expiry_interval, 120)
.keep_alive(30)
.async_run(boost::asio::detached); // Restart the Client again.
// Use the Client...
ioc.run();
}
```
[endsect] [/reusing_the_client]
[endsect] [/disconnecting_the_client]

View File

@ -0,0 +1,28 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:asio_compliance Compliance with Boost.Asio]
Every asynchronous operation in __Asio__ has associated characteristics that specify their behaviour.
* An *allocator* determines how the asynchronous operations allocate memory resources.
* A *cancellation slot* determines how the asynchronous operations support cancellation.
* An *executor* determines the queuing and execution strategy for completion handlers.
This section expands further into the roles of allocators,
cancellation slots, and executors, highlighting their integration and usage within the __Client__.
* See [link async_mqtt5.asio_compliance.allocators Allocators] for more information about how the
__Client__ supports and uses associated allocators.
* See [link async_mqtt5.asio_compliance.per_op_cancellation Per-Operation Cancellation] for more information about how
asynchronous operations within the __Client__ support cancellation.
* See [link async_mqtt5.asio_compliance.executors Executors] for more information about executors.
[include 08_allocators.qbk]
[include 09_per_op_cancellation.qbk]
[include 10_executors.qbk]
[endsect] [/asio_compliance]

28
doc/qbk/08_allocators.qbk Normal file
View File

@ -0,0 +1,28 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:allocators Allocators]
Every asynchronous operation has an associated allocator
designed to provide the operation with a consistent and stable memory source for the duration of its execution.
This allocated memory, referred to as per-operation stable memory resources (POSMs),
remains retained for the lifetime of the operation, ensuring its availability whenever needed.
Asynchronous operations may utilise POSMs in numerous ways.
See __ASIO_ALLOCATORS__ and __ASIO_CUSTOM_MEMORY_ALLOCATION__ for more information.
The __Client__ supports and utilises allocators associated with `async_xxx`'s handlers
to store the state associated with the operation.
Specifically, the allocator is used to reserve memory for MQTT Control Packet bytes
(__PUBLISH__, __SUBSCRIBE__,...) created by each `async_xxx` operation request.
Moreover, the __Client__'s internal packet queue
(described in the section ['Efficient bandwidth usage with packet queuing] in [link async_mqtt5.optimising_communication Optimising communication])
associates the allocator of the first packet in the queue to the low-level __Asio__ function
`async_write` on the transport layer.
Lastly, the __Client__ uses [@boost:doc/html/boost_asio/reference/recycling_allocator.html `boost::asio::recycling_allocator`]
to allocate memory for its internal operations and components.
[endsect][/allocators]

View File

@ -0,0 +1,95 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:per_op_cancellation Per-Operation Cancellation]
[nochunk]
In __Asio__, various objects such as sockets and timers offer the ability to terminate all ongoing asynchronous operations
globally through their `close` or `cancel` member functions.
Beyond this global cancellation capability, __Asio__ also provides mechanisms for cancelling specific asynchronous
operations on an individual basis.
This individual per-operation cancellation is enabled by specifying that a completion handler has an associated cancellation slot.
If the asynchronous operation supports cancellation, then it will install a cancellation handler into the associated slot.
The cancellation handler will be invoked when the user emits a cancellation signal into the cancellation slot.
See __ASIO_PER_OP_CANCELLATION__ for more information.
The utility of per-operation cancellation becomes particularly evident when using `boost::asio::experimental::parallel_group` to launch
work performed in parallel and wait for one or all of the operations to complete.
For instance, in a parallel group awaiting the completion of any one operation among several,
completing one operation will trigger individual cancellation of all the remaining operations.
The same concept applies to the `awaitable operator ||`, which runs two awaitables in parallel,
waiting for one to complete and subsequently cancelling the remaining one.
See __ASIO_PARALLEL_GROUP__ for more information.
Within the __Client__, every asynchronous function is designed to support individual per-operation cancellation.
This allows for associating of a cancellation slot with any `async_xxx` function call, enabling the emission of a cancellation signal as needed.
The impact of emitting a cancellation signal varies depending on the signal type (terminal, total, partial) and the operation being cancelled.
Detailed descriptions of how cancellation signals affect each `async_xxx` function
are provided in the ['Per-Operation Cancellation] paragraph in their respective sections of the __Client__ reference documentation.
[heading Example: associating a cancellation slot with an asynchronous operation]
This example illustrates associating a cancellation slot with a [refmem mqtt_client async_publish] operation
and emitting a terminal cancellation signal.
Executing this sequence effectively results in the immediate cancellation of the entire client operation,
mirroring the outcome of invoking [refmem mqtt_client cancel] directly.
If a total or partial cancellation signal were issued instead of a terminal one, the implications would be less severe.
In such cases, the cancellation would specifically target resending the __PUBLISH__ packet,
preventing it from being retransmitted should the client reconnect during the ongoing operation.
```
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
boost::asio::cancellation_signal signal;
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
boost::asio::bind_cancellation_slot(
signal.slot(),
[&client](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::puback_props props ) {
std::cout << ec.message() << std::endl;
}
)
);
signal.emit(boost::asio::cancellation_type_t::terminal);
```
[section:parallel_group parallel_group/operator || and asynchronous functions in the mqtt_client]
As a result of supporting per-operation cancellation,
all the asynchronous functions with the __Client__ can be used in `parallel_group` or with `awaitable operator ||`.
This feature is especially beneficial for executing operations that require a timeout mechanism.
Below are two examples illustrating how to implement a timeout:
* [link async_mqtt5.timeout_with_parallel_group timeout_with_parallel_group.cpp]
* [link async_mqtt5.timeout_with_awaitable_operators timeout_with_awaitable_operators.cpp]
[endsect] [/parallel_group]
[section:protocol_level_cancellation About protocol-level cancellation]
In the context of __Client__, the handling of cancellation signals varies across different asynchronous operations.
Except for [refmem mqtt_client async_receive], all other `async_xxx` operations respond to a terminal cancellation signal by invoking [refmem mqtt_client cancel].
These operations will halt the resending of certain packets for total and partial cancellation signals.
It is worth noting that cancelling an `async_xxx` operation during an ongoing protocol exchange is not implemented because of a design decision
to prevent protocol breaches.
For example, if [refmem mqtt_client async_publish] with QoS 2 is in the middle of a communication with the Broker
[footnote Publishing with QoS 2 involves sending a __PUBLISH__ packet, receiving a __PUBREC__ acknowledgement from the Broker, transmitting a __PUBREL__ packet, and finally receiving a __PUBCOMP__ packet.]
and an attempt to cancel it is made, it could lead to a protocol violation.
For instance, if the operation is cancelled after a __PUBREC__ packet has been received from the Broker but before sending the __PUBREL__ packet,
that would breach the MQTT protocol by failing to send a necessary packet and leave the connection with the Broker in an invalid state.
Therefore, the design of __Client__'s cancellation strategy carefully avoids these pitfalls to ensure continuous protocol compliance.
[endsect] [/protocol_level_cancellation]
[endsect] [/per_op_cancellation]

67
doc/qbk/10_executors.qbk Normal file
View File

@ -0,0 +1,67 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:executors Executors]
Every asynchronous operation has an associated executor that determines how the completion handlers are queued and run.
Asynchronous operations use the associated executor to track the existence of the work of asynchronous tasks,
schedule the completion handlers for execution,
and prevent re-entrant execution of the completion handlers to avoid recursion and potential stack overflow issues.
Every asynchronous operation within the __Client__ is defined as a composed operation.
This implies that each `async_xxx` operation is a sequence consisting of an initiating function,
a series of intermediate asynchronous operations, and a final completion handler.
Upon creating an instance of the __Client__, it is necessary to provide an executor or an __ExecutionContext__.
The specified executor (or __ExecutionContext__'s executor) will become the default executor associated with the __Client__
and will be used for the execution of all the intermediate operations and a final completion handler for all asynchronous operations
that have not bound an executor.
If an executor is bound to an asynchronous operation, that executor will be used instead.
The [refmem mqtt_client async_run] operation starts the __Client__, which initiates a series of internal asynchronous operations, all of which need an executor.
If the [refmem mqtt_client async_run] is called with a completion handler that has an associated executor,
then all the internal asynchronous operations will associate the same executor.
[important
The same executor *must* execute [refmem mqtt_client async_run] and all the subsequent `async_xxx` operations.
]
The following examples will demonstrate the previously described interactions.
[heading Example: using the constructor's executor as the default associated executor]
In this code snippet, the __Client__ is constructed with a strand.
Consequently, the __Client__ adopts the strand as its new default executor,
which is used to execute the [refmem mqtt_client async_publish] operation.
```
int main() {
boost::asio::io_context ioc;
// Construct the Client with a strand.
auto strand = boost::asio::make_strand(ioc.get_executor());
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(strand);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// This asynchronous operation will use the default associated executor,
// which is the strand with which the Client is constructed.
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&client, &strand](async_mqtt5::error_code /* ec */) {
assert(strand.running_in_this_thread());
client.cancel();
}
);
ioc.run();
}
```
[endsect] [/executors]

View File

@ -0,0 +1,92 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:multithreading Using the mqtt_client in a multithreaded environment]
[nochunk]
This chapter provides information about thread safety of the __Client__ and other __Asio__-compliant objects
and provides examples of how to write thread-safe code in multithreaded environments.
[section:thread_safety Thread safety of ASIO-compliant objects]
A common misconception exists regarding the "thread safety" of ASIO-compliant asynchronous objects,
specifically around the belief that initialising such an object with a __STRAND__
[footnote An executor that provides serialised handler execution.]
executor allows its asynchronous functions (`async_xxx`) to be freely called from any executor or thread.
That is not correct. Those `async_xxx` functions themselves *must* be called from within the same executor.
Every `async_xxx` function in every ASIO-compliant object begins by executing some initiation code before
typically proceeding to call an intermediate lower-level ASIO `async_xxx` function, with an adapted handler serving as the callback.
It is worth noting that the thread safety of this initiation code,
which is called directly by the [@boost:doc/html/boost_asio/reference/async_initiate.html `boost::asio::async_initiate`]
function and executed by the same executor that called the `async_xxx` function, is not guaranteed
and depends on the implementation itself.
This uncertainty around thread safety is what the notation "Thread Safety: Shared objects: Unsafe" means,
which appears in the documentation for any ASIO-compliant object.
Consequently, similar to the other ASIO-compliant objects, the __Client__ object *is not thread-safe*.
Invoking its member functions concurrently from separate threads will result in a race condition.
This design choice is intentional and offloads the responsibility of managing concurrency to the user.
Given that many applications using __Asio__ often operate in a single-threaded environment for better performance,
ASIO-compliant objects do not want to pay the cost of the overhead associated with mutexes and other synchronization mechanisms.
Instead, it encourages developers to implement their own concurrency management strategies, tailoring them to the specific needs of their applications.
[endsect] [/thread_safety]
[section:executors_threads_strands Executors, threads, and strands]
Before delving into thread-safe programming, it is essential to understand the distinction between executors and threads.
Executors are not threads but mechanisms for scheduling how and when work gets done.
An executor can distribute tasks across multiple threads, and a single thread can execute tasks from multiple executors.
Thus, when several threads invoke __IOC_RUN__ on the same __IOC__,
the underlying executor of that `io_context` has the flexibility to assign tasks to any of those threads.
A __STRAND__ executor is particularly important in maintaining thread safety and managing multithreading.
As outlined earlier, this type of executor guarantees that tasks assigned to it are executed in a serialised manner,
preventing concurrent execution.
It is important to note that this serialisation does not mean that a single thread handles all tasks within a strand.
If the `io_context` associated with a strand operates across multiple threads,
these threads can independently undertake tasks within the strand.
However, these tasks are executed in a non-concurrent fashion as guaranteed by the strand.
Refer to __ASIO_STRANDS__ for more details.
[endsect] [/executors_threads_strands]
[section:thread_safe_code Writing thread-safe code]
As mentioned previously, it is the user's responsibility to ensure that none of the __Client__'s member functions
are called concurrently from separate threads.
To achieve thread safety in a multithreaded environment,
all the __Client__'s member functions must be executed within the same implicit
[footnote Only one thread is calling __IOC_RUN__.]
or explicit strand.
Specifically, use __POST__ or __DISPATCH__ to delegate a function call to a strand,
or __CO_SPAWN__ to spawn the coroutine into the strand.
For asynchronous functions, this will ensure that the initiation code is executed within the strand in a thread-safe manner.
The associated executor of all `async_xxx`'s completion handlers must be the same strand.
This will guarantee that the entire sequence of operations
- from the initiation code through any intermediate operations to the execution of the completion handler -
is carried out within the strand, thereby ensuring thread safety.
[important
To conclude, to achieve thread safety,
all the member functions of the __Client__ *must* be executed in *the same strand*.
This strand *must* be the associated executor of all the completion handlers across
all `async_xxx` invocations.
]
The examples below demonstrate how to publish a "Hello World" Application Message
in a multithreaded setting using callbacks (`post`/`dispatch`) and coroutines (`co_spawn`):
* [link async_mqtt5.hello_world_in_multithreaded_env hello_world_in_multithreaded_env.cpp]
* [link async_mqtt5.hello_world_in_coro_multithreaded_env hello_world_in_coro_multithreaded_env.cpp]
[endsect] [/thread_safe_code]
[endsect] [/multithreading]

65
doc/qbk/15_examples.qbk Normal file
View File

@ -0,0 +1,65 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:examples Examples]
[nochunk]
The following list contains all the examples that showcase how to use the __Client__:
[variablelist
[
[[link async_mqtt5.publisher publisher.cpp]]
[Shows how to use the __Client__ as a publisher. The __Client__ publishes sensor readings every `5 seconds`.]
]
[
[[link async_mqtt5.receiver receiver.cpp]]
[Shows how to use the __Client__ as a receiver. The __Client__ subscribes and indefinitely receives Application Messages from the Broker.]
]
[
[[link async_mqtt5.hello_world_over_tcp hello_world_over_tcp.cpp]]
[Publishes a "Hello World" message via TCP/IP.]
]
[
[[link async_mqtt5.hello_world_over_tls hello_world_over_tls.cpp]]
[Publishes a "Hello World" message via TLS/SSL.]
]
[
[[link async_mqtt5.hello_world_over_websocket_tcp hello_world_over_websocket_tcp.cpp]]
[Publishes a "Hello World" message via Websocket/TLS.]
]
[
[[link async_mqtt5.hello_world_over_websocket_tls hello_world_over_websocket_tls.cpp]]
[Publishes a "Hello World" message via Websocket/TLS.]
]
[
[[link async_mqtt5.multiflight_client multiflight_client.cpp]]
[Shows how to use the __Client__ to simultaneously dispatch multiple requests.]
]
[
[[link async_mqtt5.timeout_with_parallel_group timeout_with_parallel_group.cpp]]
[
Shows how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using parallel group.
]
]
[
[[link async_mqtt5.timeout_with_awaitable_operators timeout_with_awaitable_operators.cpp]]
[
Shows how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using awaitable operators.
]
]
[
[[link async_mqtt5.hello_world_in_multithreaded_env hello_world_in_multithreaded_env.cpp]]
[Shows how to publish a "Hello World" message in a multithreaded environment using callbacks (`post`/`dispatch`).]
]
[
[[link async_mqtt5.hello_world_in_coro_multithreaded_env hello_world_in_coro_multithreaded_env.cpp]]
[Shows how to publish a "Hello World" message in a multithreaded environment using coroutines (`co_spawn`).]
]
]
[endsect][/examples]

View File

@ -1,111 +0,0 @@
[section:completion_tokens Completion tokens]
The __Client__ is built upon __Asio__ and thus follows the same principles.
This section illustrates the usage of __Client__ async
functions with different __CompletionToken__.
# [link async_mqtt5.examples.callbacks Async functions with callbacks]
# [link async_mqtt5.examples.cpp20_coroutines Async functions with C++20 coroutines]
# [link async_mqtt5.examples.futures Async functions with futures]
[endsect]
[section:callbacks Async functions with callbacks]
The following list is a reference on how to use asynchrous functions in __Client__ with callbacks.
[import ../../../example/callbacks.cpp]
[h4 Publish]
[publish_callback]
[h4 Subscribe]
[subscribe_callback]
[h4 Receive]
[receive_callback]
[h4 Unsubscribe]
[unsubscribe_callback]
[h4 Disconnect]
[disconnect_callback]
[endsect]
[section:cpp20_coroutines Async functions with C++20 coroutines]
This example demonstrates how to use __Client__ asynchrous functions with C++20 coroutines
using __USE_AWAITABLE__.
[import ../../../example/cpp20_coroutines.cpp]
[h2 use_awaitable]
In this section, each asynchronous function is invoked with __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 use_nothrow_awaitable section.
[h4 Publish]
[publish_coro]
[h4 Subscribe]
[subscribe_coro]
[h4 Receive]
[receive_coro]
[h4 Unsubscribe]
[unsubscribe_coro]
[h4 Disconnect]
[disconnect_coro]
[h2 use_nothrow_awaitable]
The following examples will use a modified completion token.
Using this completion token instead of __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.
[no_throw_awaitable]
[h4 Publish]
[publish_coro_nothrow]
[h4 Subscribe]
[subscribe_coro_nothrow]
[h4 Receive]
[receive_coro_nothrow]
[h4 Unsubscribe]
[unsubscribe_coro_nothrow]
[h4 Disconnect]
[disconnect_coro_nothrow]
[endsect]
[section:futures Async functions with futures]
The following list is a reference on how to use the mqtt_client with __USE_FUTURE__ as the completion token.
Each get() call on std::future will block the current thread and wait until the future has a valid result.
That is why it is essential to ensure that the execution context is running in more than one thread.
[import ../../../example/futures.cpp]
[h4 Publish]
[publish_future]
[h4 Subscribe]
[subscribe_future]
[h4 Receive]
[receive_future]
[h4 Unsubscribe]
[unsubscribe_future]
[h4 Disconnect]
[disconnect_future]
[endsect]

View File

@ -0,0 +1,90 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[block'''<part>''']
[section:hello_world_over_tcp Hello World over TCP/IP]
This example illustrates the process of setting up the Client to connect to the Broker via TCP/IP and publish a "Hello World!" message.
[import ../../../example/hello_world_over_tcp.cpp]
[hello_world_over_tcp]
[endsect] [/hello_world_over_tcp]
[section:hello_world_over_tls Hello World over TLS/SSL]
This example illustrates the process of setting up the Client to connect to the Broker via TLS/SSL and publish a "Hello World!" message.
[import ../../../example/hello_world_over_tls.cpp]
[hello_world_over_tls]
[endsect] [/hello_world_over_tls]
[section:hello_world_over_websocket_tcp Hello World over Websocket/TCP]
This example illustrates the process of setting up the Client to connect to the Broker via Websocket/TCP and publish a "Hello World!" message.
[import ../../../example/hello_world_over_websocket_tcp.cpp]
[hello_world_over_websocket_tcp]
[endsect] [/hello_world_over_websocket_tcp]
[section:hello_world_over_websocket_tls Hello World over Websocket/TLS]
This example illustrates the process of setting up the Client to connect to the Broker via Websocket/TLS and publish a "Hello World!" message.
[import ../../../example/hello_world_over_websocket_tls.cpp]
[hello_world_over_websocket_tls]
[endsect] [/hello_world_over_websocket_tls]
[section:publisher The publisher]
This example shows how to use __Client__ as a publisher that publishes sensor readings every `5` seconds.
The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ as the completion token.
[import ../../../example/publisher.cpp]
[publisher]
[endsect]
[section:receiver The receiver]
This example shows how to use __Client__ as a receiver.
The __Client__ subscribes and indefinitely receives Application Messages from the Broker.
The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ as the completion token.
[import ../../../example/receiver.cpp]
[receiver]
[endsect]
[section:multiflight_client The multiflight Client]
This example shows how to use __Client__ to simultaneously dispatch multiple requests.
[import ../../../example/multiflight_client.cpp]
[multiflight_client]
[endsect]
[section:timeout_with_parallel_group Timed MQTT operations with parallel group]
This example demonstrates how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using parallel group.
Specifically, in this example, the __Client__ will subscribe to a Topic and try to receive a message from the Topic within `5 seconds`.
[import ../../../example/timeout_with_parallel_group.cpp]
[timeout_with_parallel_group]
[endsect]
[section:timeout_with_awaitable_operators Timed MQTT operations with awaitable operators]
This example demonstrates how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using awaitable operators.
Specifically, in this example, a call to [refmem mqtt_client async_publish] and [refmem mqtt_client async_disconnect] must complete
within `5 seconds`. Otherwise, they will be cancelled.
[import ../../../example/timeout_with_awaitable_operators.cpp]
[timeout_with_awaitable_operators]
[endsect]
[section:hello_world_in_multithreaded_env Hello World in a multithreaded environment using callbacks]
This example demonstrates how to publish a "Hello World" message in a multithreaded environment using callbacks (`post`/`dispatch`).
[import ../../../example/hello_world_in_multithreaded_env.cpp]
[hello_world_in_multithreaded_env]
[endsect]
[section:hello_world_in_coro_multithreaded_env Hello World in a multithreaded environment using coroutines]
This example demonstrates how to publish a "Hello World" message in a multithreaded environment using coroutines (`co_spawn`).
[import ../../../example/hello_world_in_coro_multithreaded_env.cpp]
[hello_world_in_coro_multithreaded_env]
[endsect]
[block'''</part>''']

View File

@ -1,36 +0,0 @@
[section:network_connection Establishing a network connection with different protocols ]
The __MQTT__ protocol requires the underlying transport protocol that ensures
orderly, lossless transmission of byte streams between the Client and Server in both directions.
The following examples demonstrate how to establish a network connection using suitable
transport protocols such as TCP/IP, TLS/SSL, and WebSocket.
[import ../../../example/network_connection.cpp]
[h3 TCP/IP connection]
To create a TCP/IP connection with a Broker, initialise __Client__ with __TCP_SOCKET__ as the __StreamType__.
[tcp]
[h3 TLS/SSL connection]
To establish a secure and encrypted connection using the TLS/SSL protocol, supply a context object that meets the __TlsContext__ requirements.
Additionally, initialise __Client__ with an underlying stream that implements TLS/SSL protocol as the __StreamType__.
This example will demonstrate how to set up an SSL connection using __SSL_CONTEXT__ and __SSL_STREAM__.
To use SSL support in __Asio__, __OPENSSL__ is required.
[tls]
[h3 WebSocket connection]
The WebSocket connection can be established over an unencrypted TCP/IP connection or an encrypted TLS/SSL connection.
The following example will showcase setting up WebSocket over TCP/IP and WebSocket over TLS/SLL connection using
__WEBSOCKET_STREAM__.
[h4 WebSocket over TCP/IP]
[websocket_tcp]
[h4 WebSocket over TLS/SSL]
[websocket_tls]
Once the __Client__ has been initialised with a suitable __StreamType__, it is prepared for configuration and utilisation.
[endsect]

View File

@ -1,23 +0,0 @@
[section:publisher The publisher]
This example will show how to use __Client__ as a publisher.
The __Client__ will use TCP to connect to the Broker, and __USE_AWAITABLE__ as the completion token.
To modify the connection type, refer to [link async_mqtt5.examples.network_connection Establishing a network connection with different protocols].
To use different completion token, refer to [link async_mqtt5.examples.completion_tokens Completion tokens].
[import ../../../example/publisher.cpp]
[publisher]
[endsect]
[section:receiver The receiver]
This example will show how to use __Client__ as a receiver.
The __Client__ will use TCP to connect to the Broker, and __USE_AWAITABLE__ as the completion token.
To modify the connection type, refer to [link async_mqtt5.examples.network_connection Establishing a network connection with different protocols].
To use different completion token, refer to [link async_mqtt5.examples.completion_tokens Completion tokens].
[import ../../../example/receiver.cpp]
[receiver]
[endsect]

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:Error_handling Error handling]
The table below provides a reference of all __ERROR_CODE__ instances that asynchronous operations

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:ExecutionContext ExecutionContext concept]
`ExecutionContext` represents a place where function objects will be executed.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:StreamType StreamType concept]
`StreamType` represents the transport protocol type used to transfer stream of bytes.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:TlsContext TlsContext concept]
`TlsContext` represents an object that defines user's configuration

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:is_authenticator is_authenticator concept]
A type `Authenticator` satisfies `is_authenticator` concept if it satisifes the requirements listed below.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:auth_props AUTH properties]
The last field in the Variable header of __AUTH__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:connack_props CONNACK properties]
The last field in the Variable header of __CONNACK__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:connect_props CONNECT properties]
The last field in the Variable header of __CONNECT__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:disconnect_props DISCONNECT properties]
The last field in the Variable header of __DISCONNECT__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:puback_props PUBACK properties]
The last field in the Variable header of __PUBACK__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:pubcomp_props PUBCOMP properties]
The last field in the Variable header of __PUBCOMP__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:publish_props PUBLISH properties]
The last field in the Variable header of __PUBLISH__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:pubrec_props PUBREC properties]
The last field in the Variable header of __PUBREC__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:pubrel_props PUBREL properties]
The last field in the Variable header of __PUBREL__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:suback_props SUBACK properties]
The last field in the Variable header of __SUBACK__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:subscribe_props SUBSCRIBE properties]
The last field in the Variable header of __SUBSCRIBE__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:unsuback_props UNSUBACK properties]
The last field in the Variable header of __UNSUBACK__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:unsubscribe_props UNSUBSCRIBE properties]
The last field in the Variable header of __UNSUBSCRIBE__ packet is a set of Properties.
A set contains a Property Length followed by the Properties.

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:will_props Will properties]
The Will Properties consist of the properties that determine when to publish the [reflink2 will Will] Message
and the Application Message properties to be sent with the [reflink2 will Will] Message.

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE library PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN" "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
<!--
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
-->
<informaltable frame="all">
<tgroup cols="4">
<colspec colname="a"/><colspec colname="b"/><colspec colname="c"/><colspec colname="d"/>
@ -31,7 +37,6 @@
<bridgehead renderas="sect3">Enumerations</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.auth_step_e">auth_step_e</link></member>
<member><link linkend="async_mqtt5.ref.connection.error">connection::error</link></member>
<member><link linkend="async_mqtt5.ref.client.error">client::error</link></member>
<member><link linkend="async_mqtt5.ref.disconnect_rc_e">disconnect_rc_e</link></member>
<member><link linkend="async_mqtt5.ref.qos_e">qos_e</link></member>

View File

@ -1,3 +1,9 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:Reason_codes Reason codes]
This section lists all possible __REASON_CODE__ instances representing

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE library PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN" "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
<!--
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
-->
<informaltable frame="all">
<tgroup cols="3">
<colspec colname="a"/><colspec colname="b"/><colspec colname="c"/>

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!--
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
-->
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:preserve-space elements="para"/>
@ -20,10 +26,6 @@
-->
<xsl:template match="/doxygen">
<xsl:text>
[section:ref Reference]
[xinclude quickref.xml]
[include Error_handling.qbk]
[include concepts/ExecutionContext.qbk]
[include concepts/StreamType.qbk]
@ -70,10 +72,6 @@
</xsl:choose>
</xsl:for-each>
<xsl:value-of select="$newline"/>
<xsl:text>[endsect]</xsl:text>
<xsl:value-of select="$newline"/>
</xsl:template>
<!--========== Utilities ==========-->
@ -1707,7 +1705,6 @@
</xsl:if>
[endsect]
<xsl:if test="$overload-count &gt; 1 and $overload-position = $overload-count">
[endsect]
</xsl:if>

View File

@ -8,24 +8,10 @@ if(PROJECT_IS_TOP_LEVEL)
find_package(async-mqtt5 REQUIRED)
endif()
function(add_example name)
add_executable("${name}" ${ARGN})
target_compile_features("${name}" PRIVATE cxx_std_20) # for coroutines
target_link_libraries("${name}" PRIVATE Async::MQTT5)
endfunction()
# set(EXAMPLE <your-source-file>.cpp)
set(EXAMPLE hello_world_over_tcp.cpp)
foreach(f publisher receiver)
add_example("${f}" "${f}.cpp")
endforeach()
set(EXAMPLES
tcp.cpp
openssl_tls.cpp
websocket_tcp.cpp
websocket_tls.cpp
)
find_package(OpenSSL REQUIRED)
add_executable(examples src/run_examples.cpp ${EXAMPLES})
target_compile_features(examples PRIVATE cxx_std_17)
target_link_libraries(examples PRIVATE Async::MQTT5 OpenSSL::SSL)
find_package(OpenSSL REQUIRED) # if you require SSL connection
add_executable(example ${EXAMPLE})
target_compile_features(example PRIVATE cxx_std_17) # or cxx_std_20
target_link_libraries(example PRIVATE Async::MQTT5 OpenSSL::SSL)

View File

@ -1,115 +0,0 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
namespace asio = boost::asio;
using stream_type = asio::ip::tcp::socket;
using client_type = async_mqtt5::mqtt_client<stream_type>;
void run_with_callbacks(client_type& client) {
//[publish_callback
// Publish an Application Message with QoS 0.
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"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<async_mqtt5::qos_e::at_least_once>(
"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<async_mqtt5::qos_e::exactly_once>(
"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_callback
// 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<reason_code>, suback_props)
[](async_mqtt5::error_code ec,
std::vector<async_mqtt5::reason_code> 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_callback
// 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_callback
// Unsubscribe from the Topic.
client.async_unsubscribe("test/mqtt-test", async_mqtt5::unsubscribe_props {},
//Callback with signature void (error_code, std::vector<reason_code>, unsuback_props)
[](async_mqtt5::error_code ec,
std::vector<async_mqtt5::reason_code> 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_callback
// 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)
.async_run(asio::detached);
run_with_callbacks(c);
ioc.run();
}

View File

@ -1,177 +0,0 @@
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
namespace asio = boost::asio;
using stream_type = asio::ip::tcp::socket;
using client_type = async_mqtt5::mqtt_client<stream_type>;
asio::awaitable<void> coroutine(client_type& client) {
//[publish_coro
// 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<async_mqtt5::qos_e::at_most_once>(
"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<async_mqtt5::qos_e::at_least_once>(
"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<async_mqtt5::qos_e::exactly_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
asio::use_awaitable
);
//]
//[subscribe_coro
// Subscribe to a single Topic.
// The handler signature for this function is void (error_code, std::vector<reason_code>, suback_props).
// With asio::use_awaitable as a completion token, the co_await
// will return std::vector<reason_code> and suback_props.
auto [sub_codes, sub_props] = co_await client.async_subscribe(
{ "test/mqtt-test", { async_mqtt5::qos_e::exactly_once } },
async_mqtt5::subscribe_props {}, asio::use_awaitable
);
//]
//[receive_coro
// 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_coro
// Unsubscribe from the Topic.
// The handler signature for this function is void (error_code, std::vector<reason_code>, unsuback_props).
// With asio::use_awaitable as a completion token, the co_await
// will return std::vector<reason_code> and unsuback_props.
auto [unsub_codes, unsub_props] = co_await client.async_unsubscribe(
"test/mqtt-test", async_mqtt5::unsubscribe_props {},
asio::use_awaitable
);
//]
//[disconnect_coro
// 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;
}
//[no_throw_awaitable
constexpr auto use_nothrow_awaitable = asio::as_tuple(asio::use_awaitable);
//]
asio::awaitable<void> nothrow_coroutine(client_type& client) {
//[publish_coro_nothrow
async_mqtt5::error_code ec;
async_mqtt5::reason_code rc;
std::tie(ec) = co_await client.async_publish<async_mqtt5::qos_e::at_most_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
use_nothrow_awaitable
);
async_mqtt5::puback_props puback_props;
std::tie(ec, rc, puback_props) = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
use_nothrow_awaitable
);
async_mqtt5::pubcomp_props pubcomp_props;
std::tie(ec, rc, pubcomp_props) = co_await client.async_publish<async_mqtt5::qos_e::exactly_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
use_nothrow_awaitable
);
//]
//[subscribe_coro_nothrow
std::vector<async_mqtt5::reason_code> rcs;
async_mqtt5::suback_props suback_props;
std::tie(ec, rcs, suback_props) = co_await client.async_subscribe(
{ "test/mqtt-test", { async_mqtt5::qos_e::exactly_once } },
async_mqtt5::subscribe_props {}, use_nothrow_awaitable
);
//]
//[receive_coro_nothrow
std::string topic, payload;
async_mqtt5::publish_props publish_props;
std::tie(ec, topic, payload, publish_props) = co_await client.async_receive(use_nothrow_awaitable);
//]
//[unsubscribe_coro_nothrow
async_mqtt5::unsuback_props unsuback_props;
std::tie(ec, rcs, unsuback_props) = co_await client.async_unsubscribe(
std::vector<std::string>{ "test/mqtt-test" }, async_mqtt5::unsubscribe_props {},
use_nothrow_awaitable
);
//]
//[disconnect_coro_nothrow
std::tie(ec) = co_await client.async_disconnect(
async_mqtt5::disconnect_rc_e::disconnect_with_will_message,
async_mqtt5::disconnect_props {},
use_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)
.async_run(asio::detached);
co_spawn(ioc.get_executor(), coroutine(c), asio::detached);
// or...
co_spawn(ioc.get_executor(), nothrow_coroutine(c), asio::detached);
ioc.run();
}
#endif

View File

@ -1,110 +0,0 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
#include <thread>
namespace asio = boost::asio;
using stream_type = asio::ip::tcp::socket;
using client_type = async_mqtt5::mqtt_client<stream_type>;
void run_with_future(client_type& client) {
//[publish_future
// Just like the asio::use_awaitable completion token
// (see ``__EXAMPLE_COROUTINE__``), the ``__USE_FUTURE__`` completion token
// will not return the error_code. Instead, it will throw an exception if an error has occurred.
std::future<void> pub_qos0_fut =
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
asio::use_future
);
pub_qos0_fut.get(); // Blocking call!
using qos1_fut_type = std::tuple<async_mqtt5::reason_code, async_mqtt5::puback_props>;
std::future<qos1_fut_type> pub_qos1_fut =
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
asio::use_future
);
auto [qos1_rc, puback_props] = pub_qos1_fut.get();
std::cout << "Publish QoS 1 Reason Code: " << qos1_rc.message() << std::endl;
using qos2_fut_type = std::tuple<async_mqtt5::reason_code, async_mqtt5::pubcomp_props>;
std::future<qos2_fut_type> pub_qos2_fut =
client.async_publish<async_mqtt5::qos_e::exactly_once>(
"test/mqtt-test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
asio::use_future
);
auto [qos2_rc, pubcomp_props] = pub_qos2_fut.get();
std::cout << "Publish QoS 2 Reason Code: " << qos2_rc.message() << std::endl;
//]
//[subscribe_future
using sub_fut_type = std::tuple<std::vector<async_mqtt5::reason_code>, async_mqtt5::suback_props>;
std::future<sub_fut_type> sub_fut = client.async_subscribe(
{ "test/mqtt-test", { async_mqtt5::qos_e::exactly_once } },
async_mqtt5::subscribe_props {}, asio::use_future
);
auto [sub_rcs, suback_props] = sub_fut.get();
std::cout << "Subscribe Reason Code: " << sub_rcs[0].message() << std::endl;
//]
//[receive_future
// Note: the get() call on async_receive future could block indefinitely if the ``__Client__``
// failed to subscribe or there are no Application Messages to be received from the subscribed Topic!
using rec_fut_type = std::tuple<std::string, std::string, async_mqtt5::publish_props>;
std::future<rec_fut_type> rec_fut = client.async_receive(asio::use_future);
auto [topic, payload, publish_props] = rec_fut.get();
std::cout << "Received message from Topic: " << topic << ", " << payload << std::endl;
//]
//[unsubscribe_future
using unsub_fut_type = std::tuple<std::vector<async_mqtt5::reason_code>, async_mqtt5::unsuback_props>;
std::future<unsub_fut_type> unsub_fut = client.async_unsubscribe(
"test/mqtt-test", async_mqtt5::unsubscribe_props {}, asio::use_future
);
auto [unsub_rcs, unsuback_props] = unsub_fut.get();
std::cout << "Unubscribe Reason Code: " << unsub_rcs[0].message() << std::endl;
//]
//[disconnect_future
std::future<void> dc_fut = client.async_disconnect(asio::use_future);
dc_fut.get();
//]
return;
}
int main(int argc, char** argv) {
// asio::io_context must be running in more than one thread!
constexpr auto thread_num = 2;
asio::io_context ioc(thread_num);
std::vector<std::thread> threads;
threads.reserve(thread_num - 1);
// Make an instance of mqtt_client. Establish a TCP connection with the Broker.
client_type c(ioc.get_executor());
c.credentials("test-client", "", "")
.brokers("mqtt.broker", 1883)
.async_run(asio::detached);
for (int i = 0; i < thread_num - 1; ++i)
threads.emplace_back([&ioc] { ioc.run(); });
run_with_future(c);
ioc.run();
for (auto& t : threads)
if (t.joinable()) t.join();
}

View File

@ -0,0 +1,91 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_in_coro_multithreaded_env
#include <vector>
#include <thread>
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
boost::asio::awaitable<void> publish_hello_world(
client_type& client,
const boost::asio::strand<boost::asio::io_context::executor_type>& strand
) {
// Confirmation that the coroutine running in the strand.
assert(strand.running_in_this_thread());
// All these function calls will be executed by the strand that is executing the coroutine.
// All the completion handler's associated executors will be that same strand
// because the Client was constructed with it as the default associated executor.
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
auto&& [ec, rc, puback_props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", "Hello world!", async_mqtt5::retain_e::no,
async_mqtt5::publish_props {}, use_nothrow_awaitable);
co_await client.async_disconnect(use_nothrow_awaitable);
co_return;
}
int main() {
// Create a multithreaded environment where 4 threads
// will be calling ioc.run().
// Number of threads that will call io_context::run().
int thread_num = 4;
boost::asio::io_context ioc(4);
// Create the remaining threads (aside of this one).
std::vector<std::thread> threads;
threads.reserve(thread_num - 1);
// Create an explicit strand from io_context's executor.
// The strand guarantees a serialised handler execution regardless of the
// number of threads running in the io_context.
boost::asio::strand strand = boost::asio::make_strand(ioc.get_executor());
// Create the Client with the explicit strand as the default associated executor.
client_type client(strand);
// Spawn the coroutine.
// The executor that executes the coroutine must be the same executor
// that is the Client's default associated executor.
co_spawn(strand, publish_hello_world(client, strand), boost::asio::detached);
// Call ioc.run() in the other threads.
for (int i = 0; i < thread_num - 1; ++i)
threads.emplace_back([&ioc] { ioc.run(); });
// Call ioc.run() on this thread.
ioc.run();
for (auto& t : threads)
if (t.joinable()) t.join();
return 0;
}
#endif
//]

View File

@ -0,0 +1,97 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_in_multithreaded_env
#include <iostream>
#include <vector>
#include <thread>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
int main() {
// Create a multithreaded environment where 4 threads
// will be calling ioc.run().
// Number of threads that will call io_context::run().
int thread_num = 4;
boost::asio::io_context ioc(4);
// Create the remaining threads (aside of this one).
std::vector<std::thread> threads;
threads.reserve(thread_num - 1);
// Create an explicit strand from io_context's executor.
// The strand guarantees a serialised handler execution regardless of the
// number of threads running in the io_context.
boost::asio::strand strand = boost::asio::make_strand(ioc.get_executor());
// Create the Client with the explicit strand as the default associated executor.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(strand);
// Configure the client.
client.brokers("<your-mqtt-broker>", 1883);
// Start the Client.
// The async_run function call must be posted/dispatched to the strand.
boost::asio::post(
strand,
[&client, &strand] {
// Considering that the default associated executor of all completion handlers is the strand,
// it is not necessary to explicitly bind it to async_run or other async_xxx's handlers.
client.async_run(boost::asio::detached);
}
);
// The async_publish function call must be posted/dispatched to the strand.
// The associated executor of async_publish's completion handler must be the same strand.
boost::asio::post(
strand,
[&client, &strand] {
assert(strand.running_in_this_thread());
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", "Hello world!", async_mqtt5::retain_e::no,
async_mqtt5::publish_props {},
// You may bind the strand to this handler, but it is not necessary
// as the strand is already the default associated handler.
// However, you must not bind it to any other executor!
[&client, &strand](
async_mqtt5::error_code ec, async_mqtt5::reason_code rc,
async_mqtt5::puback_props props
) {
assert(strand.running_in_this_thread());
std::cout << ec.message() << std::endl;
std::cout << rc.message() << std::endl;
// Stop the Client. This will cause ioc.run() to return.
client.cancel();
}
);
}
);
// Call ioc.run() on the other threads.
for (int i = 0; i < thread_num - 1; ++i)
threads.emplace_back([&ioc] { ioc.run(); });
// Call ioc.run() on this thread.
ioc.run();
for (auto& t : threads)
if (t.joinable()) t.join();
return 0;
}
//]

View File

@ -0,0 +1,38 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_tcp
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
// Construct the Client with ``__TCP_SOCKET__`` as the underlying stream.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
// 1883 is the default TCP MQTT port.
client.brokers("broker.hivemq.com", 1883)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"async-mqtt5/test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
}
//]

View File

@ -0,0 +1,92 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_tls
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
// External customization point.
namespace async_mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<boost::asio::ssl::stream<StreamBase>> {
static constexpr auto client = boost::asio::ssl::stream_base::client;
static constexpr auto server = boost::asio::ssl::stream_base::server;
};
// This client uses this function to indicate which hostname it is
// attempting to connect to at the start of the handshaking process.
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& ctx,
boost::asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
// The certificate file in the PEM format.
constexpr char ca_cert[] =
"-----BEGIN CERTIFICATE-----\n"
"...........................\n"
"-----END CERTIFICATE-----\n"
;
int main() {
boost::asio::io_context ioc;
// Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
async_mqtt5::error_code ec;
// Add the trusted certificate authority for performing verification.
context.add_certificate_authority(boost::asio::buffer(ca_cert), ec);
if (ec)
std::cout << "Failed to add certificate authority!" << std::endl;
ec.clear();
// Set peer verification mode used by the context.
// This will verify that the server's certificate is valid and signed by a trusted certificate authority.
context.set_verify_mode(boost::asio::ssl::verify_peer, ec);
if (ec)
std::cout << "Failed to set peer verification mode!" << std::endl;
ec.clear();
// Construct the Client with ``__SSL_STREAM__`` as the underlying stream
// with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type.
async_mqtt5::mqtt_client<
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>,
boost::asio::ssl::context
> client(ioc, std::move(context));
// 8883 is the default TLS MQTT port.
client.brokers("<your-mqtt-broker>", 8883)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props{},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
}
//]

View File

@ -0,0 +1,42 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_websocket_tcp
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
// Construct the Client with WebSocket/TCP as the underlying stream.
async_mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ip::tcp::socket>
> client(ioc);
// 8083 is the default Webscoket/TCP MQTT port.
client.brokers("<your-mqtt-broker>", 8083)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props{},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
}
//]

View File

@ -0,0 +1,114 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_websocket_tls
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
namespace boost::beast::websocket {
// boost::beast::websocket::async_teardown is a free function designed to initiate the asynchronous teardown of a connection.
// The specific behaviour of this function is based on the NextLayer type (Socket type) used to create the ``__WEBSOCKET_STREAM__``.
// ``__Beast__`` library includes an implementation of this function for ``__TCP_SOCKET__``.
// However, the callers are responsible for providing a suitable overload of this function for any other type,
// such as ``__SSL_STREAM__`` as shown in this example.
// See ``__BEAST_ASYNC_TEARDOWN__`` for more information.
template <typename TeardownHandler>
void async_teardown(
boost::beast::role_type role,
asio::ssl::stream<asio::ip::tcp::socket>& stream,
TeardownHandler&& handler
) {
return stream.async_shutdown(std::forward<TeardownHandler>(handler));
}
} // end namespace boost::beast::websocket
// External customization point.
namespace async_mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<boost::asio::ssl::stream<StreamBase>> {
static constexpr auto client = boost::asio::ssl::stream_base::client;
static constexpr auto server = boost::asio::ssl::stream_base::server;
};
// This client uses this function to indicate which hostname it is
// attempting to connect to at the start of the handshaking process.
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& ctx,
boost::asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
// The certificate file in the PEM format.
constexpr char ca_cert[] =
"-----BEGIN CERTIFICATE-----\n"
"...........................\n"
"-----END CERTIFICATE-----\n"
;
int main() {
boost::asio::io_context ioc;
// Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
async_mqtt5::error_code ec;
// Add the trusted certificate authority for performing verification.
context.add_certificate_authority(boost::asio::buffer(ca_cert), ec);
if (ec)
std::cout << "Failed to add certificate authority!" << std::endl;
ec.clear();
// Set peer verification mode used by the context.
// This will verify that the server's certificate is valid and signed by a trusted certificate authority.
context.set_verify_mode(boost::asio::ssl::verify_peer, ec);
if (ec)
std::cout << "Failed to set peer verification mode!" << std::endl;
ec.clear();
// Construct the Client with WebSocket/SSL as the underlying stream
// with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type.
async_mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>,
boost::asio::ssl::context
> client(ioc, std::move(context));
// 8884 is the default Websocket/TLS MQTT port.
client.brokers("<your-mqtt-broker>", 8884)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props{},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
}
//]

View File

@ -0,0 +1,47 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[multiflight_client
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// Publish with QoS 2 five times in a row without waiting for the handler
// of the previous async_publish call to be invoked.
for (auto i = 1; i <= 5; ++i)
client.async_publish<async_mqtt5::qos_e::exactly_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[i](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::pubcomp_props) {
std::cout << "Publish number " << i << " completed with: " << std::endl;
std::cout << "\t ec: " << ec.message() << std::endl;
std::cout << "\t rc: " << rc.message() << std::endl;
}
);
// We can stop the Client by using signals.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client](async_mqtt5::error_code, int) {
client.async_disconnect(boost::asio::detached);
});
ioc.run();
}
//]

View File

@ -1,176 +0,0 @@
//[network_connectiong
//[tcp
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
void tcp_setup() {
boost::asio::io_context ioc;
// Use ``__TCP_SOCKET__`` as the underlying stream.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
}
//]
//[tls
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
// External customization point.
namespace async_mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
// This client uses this funcction to indicate which hostname it is
// attempting to connect to at the start of the handshaking process.
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& ctx,
boost::asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
// The certificate file in PEM format.
constexpr char certificate[] =
"-----BEGIN CERTIFICATE-----\n"
"...........................\n"
"-----END CERTIFICATE-----\n"
;
void ssl_setup() {
boost::asio::io_context ioc;
// Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
async_mqtt5::error_code ec;
// Add the trusted certificate authority for performing verification.
context.add_certificate_authority(boost::asio::buffer(certificate), ec);
// Set peer verification mode used by the context.
// This will verify that the server's certificate is valid and signed by a trusted certificate authority.
context.set_verify_mode(boost::asio::ssl::verify_peer, ec);
// Use ``__SSL_STREAM__`` as the ``__StreamType__`` with ``__SSL_CONTEXT__`` as the ``__TlsContext__``.
async_mqtt5::mqtt_client<
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>,
boost::asio::ssl::context
> client(ioc, std::move(context));
}
//]
//[websocket_tcp
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
void websocket_tcp_setup() {
boost::asio::io_context ioc;
// Use ``[beastreflink boost__beast__websocket__stream websocket::stream<__TCP_SOCKET__>]`` as the underlying stream.
async_mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ip::tcp::socket>
> client(ioc);
}
//]
//[websocket_tls
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
constexpr char ca[] =
"-----BEGIN CERTIFICATE-----\n"
"...........................\n"
"-----END CERTIFICATE-----\n"
;
namespace boost::beast::websocket {
// ``[beastreflink boost__beast__websocket__async_teardown boost::beast::websocket::async_teardown]`` is a free function
// designed to initiate the asynchronous teardown of a connection.
// The specific behaviour of this function is based on the NextLayer type (Socket type) used to create the ``__WEBSOCKET_STREAM__``.
// ``__Beast__`` library includes an implementation of this function for ``__TCP_SOCKET__``.
// However, the callers are responsible for providing a suitable overload of this function for any other type,
// such as ``__SSL_STREAM__`` as shown in this example.
template <typename TeardownHandler>
void async_teardown(
boost::beast::role_type role,
asio::ssl::stream<asio::ip::tcp::socket>& stream,
TeardownHandler&& handler
) {
return stream.async_shutdown(std::forward<TeardownHandler>(handler));
}
} // end namespace boost::beast::websocket
namespace async_mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename streambase>
void assign_tls_sni(
const authority_path& ap,
asio::ssl::context& ctx,
asio::ssl::stream<streambase>& stream
) {
ssl_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
void websocket_tls_setup() {
boost::asio::io_context ioc;
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
async_mqtt5::error_code ec;
context.add_certificate_authority(boost::asio::buffer(ca), ec);
context.set_verify_mode(boost::asio::ssl::verify_peer, ec);
async_mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>,
boost::asio::ssl::context
> client(ioc, std::move(context));
}
//]
//]

View File

@ -1,228 +0,0 @@
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
namespace asio = boost::asio;
namespace async_mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
asio::ssl::context& /* ctx */,
asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
constexpr char mireo_ca[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDUTCCAjmgAwIBAgIUAzV59EhZA5MXluHNqRi9cBP0x9swDQYJKoZIhvcNAQEL\n"
"BQAwGDEWMBQGA1UEAwwNTWlyZW8gUm9vdCBDQTAeFw0yMjA0MDcxMzM1MjlaFw0z\n"
"MjA0MDQxMzM1MjlaMBgxFjAUBgNVBAMMDU1pcmVvIFJvb3QgQ0EwggEiMA0GCSqG\n"
"SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCin/qsHpdxT3iW0SEHhAcTfESyQcfwGtJE\n"
"jcRrGEj36X6eahyY4AF+4Mlz2vWFeW52ayGXpQKn/z4tChdN80txdY77YmEX7XE0\n"
"HHZYY6toNq/+mNX9h2HvB0GW+8+E0YfNN/HloTxDo3RT8+IovY9OSXt44vY4YtQK\n"
"JbvZIm2Q8Iuv3vfNR05uFa4HcNqFhELh10jss0xG/54Y2NvB6xdKOZ8LRQuIX+Fu\n"
"QRzMiqRFQPUJzWxbKF5I/MFiKWmAG0QNPDnlb8XtPmFTFCWY9X96wOpQOczrxT2+\n"
"+vnTxPA3aTAkz7M4yUuocZQqTlbdfdGOSAENXavewdMCyy5bQsSLAgMBAAGjgZIw\n"
"gY8wHQYDVR0OBBYEFLdUGYfJRf9mbM/fTav9U2vFI+TRMFMGA1UdIwRMMEqAFLdU\n"
"GYfJRf9mbM/fTav9U2vFI+TRoRykGjAYMRYwFAYDVQQDDA1NaXJlbyBSb290IENB\n"
"ghQDNXn0SFkDkxeW4c2pGL1wE/TH2zAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB\n"
"BjANBgkqhkiG9w0BAQsFAAOCAQEAHm5d4YUP8BYcks10UCdswLtxbMUN99fNbnYo\n"
"RMxx4EapwhEZFSNbIZvf1INJd5Po+hH5jteBeFVP+4zKqrhg3I8pjdC4josHmrhS\n"
"28OjOFWp6xNJC43BHnLpc84bH0+XIEBbk7YA6H3GjpsZ7aJkhj/JPjjNq7bmyYN7\n"
"1I9RK4PtIrNtUFbSsHZCZhf8Amtl8PrpktITECjfqCq+8uOAqP4McTIQ1JKwYy6f\n"
"O6iu0eybJCFhWYENTUQyPi1VtEwOpWNLzaXBYdj69Xg8wA/J9RZIoqXWvtHv4rPF\n"
"HGudMEIVB3y2vVLmujvQCqYPZWwbgpy5mN3F4uBNuZhTIwWRFg==\n"
"-----END CERTIFICATE-----\n"
;
void publish_qos0_openssl_tls() {
std::cout << "[Test-publish-qos0-openssl-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
error_code ec;
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-qos0-openssl-tls", "", "")
.brokers("emqtt.mireo.local", 8883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_most_once>(
"test/mqtt-test", "hello world with qos0!",
retain_e::no, publish_props{},
[&c](error_code ec) {
std::cout << "error_code: " << ec.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void publish_qos1_openssl_tls() {
std::cout << "[Test-publish-qos1-openssl-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
error_code ec;
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-qos1-openssl-tls", "", "")
.brokers("emqtt.mireo.local", 8883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_least_once>(
"test/mqtt-test", "hello world with qos1!",
retain_e::no, publish_props{},
[&c](error_code ec, reason_code rc, puback_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void publish_qos2_openssl_tls() {
std::cout << "[Test-publish-qos2-openssl-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
error_code ec;
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-qos2-openssl-tls", "", "")
.brokers("emqtt.mireo.local", 8883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::exactly_once>(
"test/mqtt-test", "hello world with qos2!",
retain_e::no, publish_props{},
[&c](error_code ec, reason_code rc, pubcomp_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void subscribe_and_receive_openssl_tls(int num_receive) {
std::cout << "[Test-subscribe-and-receive-openssl-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
error_code ec;
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-subscriber-openssl-tls", "", "")
.brokers("emqtt.mireo.local", 8883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
std::vector<subscribe_topic> topics;
topics.push_back(subscribe_topic {
"test/mqtt-test", {
qos_e::exactly_once,
no_local_e::no,
retain_as_published_e::retain,
retain_handling_e::send
}
});
c.async_subscribe(
topics, subscribe_props {},
[](error_code ec, std::vector<reason_code> codes, suback_props) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "subscribe error_code: " << ec.message() << std::endl;
std::cout << "subscribe reason_code: " << codes[0].message() << std::endl;
}
);
for (auto i = 0; i < num_receive; i++) {
c.async_receive(
[&c, i, num_receive] (
error_code ec, std::string topic,
std::string payload, publish_props
) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "message " << i + 1 << "/" << num_receive << std::endl;
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "topic: " << topic << std::endl;
std::cout << "payload: " << payload << std::endl;
if (i == num_receive - 1)
c.async_disconnect(asio::detached);
}
);
}
ioc.run();
return;
}
void run_openssl_tls_examples() {
publish_qos0_openssl_tls();
publish_qos1_openssl_tls();
publish_qos2_openssl_tls();
subscribe_and_receive_openssl_tls(1);
}

View File

@ -1,9 +1,20 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[publisher
#include <cstdlib>
#include <iostream>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/ip/tcp.hpp>
@ -12,43 +23,80 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
namespace asio = boost::asio;
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
asio::awaitable<void> client_publisher(asio::io_context& ioc) {
// Initialise the Client, establish connection to the Broker over TCP.
async_mqtt5::mqtt_client<asio::ip::tcp::socket> client(ioc);
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
int next_sensor_reading() {
srand(static_cast<unsigned int>(std::time(0)));
return rand() % 100;
}
boost::asio::awaitable<void> publish_sensor_readings(
client_type& client, boost::asio::steady_timer& timer
) {
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers("mqtt.broker", 1883) // Broker that we want to connect to. 1883 is the default TCP port.
.async_run(asio::detached); // Start the client.
client.brokers("<your-mqtt-broker>", 1883) // Broker that we want to connect to. 1883 is the default TCP port.
.async_run(boost::asio::detached); // Start the client.
// Publish an Application Message with QoS 1.
auto [rc, props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"test/mqtt-test", "my application message",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, asio::use_awaitable
);
if (rc)
std::cout << "MQTT protocol error occurred: " << rc.message() << std::endl;
for (;;) {
// Get the next sensor reading.
auto reading = std::to_string(next_sensor_reading());
// Publish some more messages...
// Publish the sensor reading with QoS 1.
auto&& [ec, rc, props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", reading,
async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, use_nothrow_awaitable
);
// An error can occur as a result of:
// a) wrong publish parameters
// b) mqtt_client::cancel is called while the Client is publishing the message
// resulting in cancellation.
if (ec) {
std::cout << "Publish error occurred: " << ec.message() << std::endl;
break;
}
// After we are done with publishing all the messages, disconnect the Client.
// Alternatively, you can also use mqtt_client::cancel.
// Regardless, you should ensure all the operations are completed before disconnecting the Client.
co_await client.async_disconnect(
async_mqtt5::disconnect_rc_e::normal_disconnection, async_mqtt5::disconnect_props {}, asio::use_awaitable
);
// Reason code is the reply from the server presenting the result of the publish operation.
std::cout << "Result of publish request: " << rc.message() << std::endl;
if (!rc)
std::cout << "Published sensor reading: " << reading << std::endl;
// Wait 5 seconds before publishing the next reading.
timer.expires_after(std::chrono::seconds(5));
auto&& [tec] = co_await timer.async_wait(use_nothrow_awaitable);
// An error occurred if we cancelled the timer.
if (tec)
break;
}
co_return;
}
int main() {
// Initialise execution context.
asio::io_context ioc;
boost::asio::io_context ioc;
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc);
// Initialise the timer.
boost::asio::steady_timer timer(ioc);
// Set up signals to stop the program on demand.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client, &timer](async_mqtt5::error_code /* ec */, int /* signal */) {
// After we are done with publishing all the messages, cancel the timer and the Client.
// Alternatively, use mqtt_client::async_disconnect.
timer.cancel();
client.cancel();
});
// Spawn the coroutine.
co_spawn(ioc.get_executor(), client_publisher(ioc), asio::detached);
co_spawn(ioc.get_executor(), publish_sensor_readings(client, timer), boost::asio::detached);
// Start the execution.
ioc.run();

View File

@ -1,9 +1,18 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[receiver
#include <iostream>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/ip/tcp.hpp>
@ -12,20 +21,15 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
namespace asio = boost::asio;
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
asio::awaitable<void> client_receiver(asio::io_context& ioc) {
// Initialise the Client, establish connection to the Broker over TCP.
async_mqtt5::mqtt_client<asio::ip::tcp::socket> client(ioc);
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers("mqtt.broker", 1883) // Broker that we want to connect to. 1883 is the default TCP port.
.async_run(asio::detached); // Start the client.
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
boost::asio::awaitable<bool> subscribe(client_type& client) {
// Configure the request to subscribe to a Topic.
async_mqtt5::subscribe_topic sub_topic = async_mqtt5::subscribe_topic {
"test/mqtt-test",
async_mqtt5::subscribe_topic sub_topic = async_mqtt5::subscribe_topic{
"<your-mqtt-topic>",
async_mqtt5::subscribe_options {
async_mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2.
async_mqtt5::no_local_e::no, // Forward message from Clients with same ID.
@ -35,45 +39,74 @@ asio::awaitable<void> client_receiver(asio::io_context& ioc) {
};
// Subscribe to a single Topic.
auto [sub_codes, sub_props] = co_await client.async_subscribe(
sub_topic, async_mqtt5::subscribe_props {}, asio::use_awaitable
auto&& [ec, sub_codes, sub_props] = co_await client.async_subscribe(
sub_topic, async_mqtt5::subscribe_props {}, use_nothrow_awaitable
);
// Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call.
// std::vector<async_mqtt5::reason_code> sub_codes contain the result of the subscribe action for every Topic.
// An error can occur as a result of:
// a) wrong subscribe parameters
// b) mqtt_client::cancel is called while the Client is in the process of subscribing
if (ec)
std::cout << "Subscribe error occurred: " << ec.message() << std::endl;
else
std::cout << "Result of subscribe request: " << sub_codes[0].message() << std::endl;
co_return !ec && !sub_codes[0]; // True if the subscription was successfully established.
}
boost::asio::awaitable<void> subscribe_and_receive(client_type& client) {
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers("<your-mqtt-broker>", 1883) // Broker that we want to connect to. 1883 is the default TCP port.
.async_run(boost::asio::detached); // Start the client.
// Before attempting to receive an Application Message from the Topic we just subscribed to,
// it is advisable to verify that the subscription succeeded.
// It is not recommended to call mqtt_client::async_receive if you do not have any
// subscription established as the corresponding handler will never be invoked.
if (!sub_codes[0])
auto [topic, payload, publish_props] = co_await client.async_receive(asio::use_awaitable);
// Receive more messages...
if (!(co_await subscribe(client)))
co_return;
// Unsubscribe from the Topic.
// Similar to mqtt_client::async_subscribe call, std::vector<async_mqtt5::reason_code> unsub_codes contain
// the result of the unsubscribe action for every Topic.
auto [unsub_codes, unsub_props] = co_await client.async_unsubscribe(
"test/mqtt-test", async_mqtt5::unsubscribe_props {},
asio::use_awaitable
);
// Note: you can unsubscribe from multiple Topics in one mqtt_client::async_unsubscribe call.
for (;;) {
// Receive an Appplication Message from the subscribed Topic(s).
auto&& [ec, topic, payload, publish_props] = co_await client.async_receive(use_nothrow_awaitable);
// Disconnect the Client.
co_await client.async_disconnect(
async_mqtt5::disconnect_rc_e::disconnect_with_will_message,
async_mqtt5::disconnect_props {},
asio::use_awaitable
);
if (ec == async_mqtt5::client::error::session_expired) {
// The Client has reconnected, and the prior session has expired.
// As a result, any previous subscriptions have been lost and must be reinstated.
if (co_await subscribe(client))
continue;
else
break;
} else if (ec)
break;
std::cout << "Received message from the Broker" << std::endl;
std::cout << "\t topic: " << topic << std::endl;
std::cout << "\t payload: " << payload << std::endl;
}
co_return;
}
int main() {
// Initialise execution context.
asio::io_context ioc;
boost::asio::io_context ioc;
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc);
// Set up signals to stop the program on demand.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client](async_mqtt5::error_code /* ec */, int /* signal */) {
// After we are done with publishing all the messages, cancel the timer and the Client.
// Alternatively, use mqtt_client::async_disconnect.
client.cancel();
});
// Spawn the coroutine.
co_spawn(ioc, client_receiver(ioc), asio::detached);
co_spawn(ioc, subscribe_and_receive(client), boost::asio::detached);
// Start the execution.
ioc.run();

View File

@ -1,15 +0,0 @@
void run_tcp_examples();
void run_openssl_tls_examples();
void run_websocket_tcp_examples();
void run_websocket_tls_examples();
int main() {
run_tcp_examples();
run_openssl_tls_examples();
run_websocket_tcp_examples();
run_websocket_tls_examples();
return 0;
}

View File

@ -1,151 +0,0 @@
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
namespace asio = boost::asio;
void publish_qos0_tcp() {
std::cout << "[Test-publish-qos0-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ip::tcp::socket;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
connect_props props;
props[prop::maximum_packet_size] = 1024;
c.credentials("test-qos0-tcp", "", "")
.brokers("emqtt.mireo.local", 1883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.connect_properties(std::move(props))
.async_run(asio::detached);
c.async_publish<qos_e::at_most_once>(
"test/mqtt-test", "hello world with qos0!",
retain_e::no, publish_props{},
[&c](error_code ec) {
std::cout << "error_code: " << ec.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
}
void publish_qos1_tcp() {
std::cout << "[Test-publish-qos1-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ip::tcp::socket;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-qos1-tcp", "", "")
.brokers("emqtt.mireo.local", 1883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_least_once>(
"test/mqtt-test", "hello world with qos1!",
retain_e::no, publish_props {},
[&c](error_code ec, reason_code rc, puback_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
}
void publish_qos2_tcp() {
std::cout << "[Test-publish-qos2-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ip::tcp::socket;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-qos2-tcp", "", "")
.brokers("emqtt.mireo.local", 1883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::exactly_once>(
"test/mqtt-test", "hello world with qos2!",
retain_e::no, publish_props {},
[&c](error_code ec, reason_code rc, pubcomp_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
}
void subscribe_and_receive_tcp(int num_receive) {
std::cout << "[Test-subscribe-and-receive-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = asio::ip::tcp::socket;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-subscriber-tcp", "", "")
.brokers("emqtt.mireo.local", 1883)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_subscribe(
{ "test/mqtt-test", { qos_e::exactly_once } }, subscribe_props {},
[](error_code ec, std::vector<reason_code> codes, suback_props) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "subscribe error_code: " << ec.message() << std::endl;
std::cout << "subscribe reason_code: " << codes[0].message() << std::endl;
}
);
for (auto i = 0; i < num_receive; i++) {
c.async_receive(
[&c, i, num_receive] (
error_code ec, std::string topic,
std::string payload, publish_props
) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "message " << i + 1 << "/" << num_receive << std::endl;
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "topic: " << topic << std::endl;
std::cout << "payload: " << payload << std::endl;
if (i == num_receive - 1)
c.async_disconnect(asio::detached);
}
);
}
ioc.run();
}
void run_tcp_examples() {
publish_qos0_tcp();
publish_qos1_tcp();
publish_qos2_tcp();
subscribe_and_receive_tcp(1);
}

View File

@ -0,0 +1,73 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[timeout_with_awaitable_operators
#include <iostream>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
boost::asio::awaitable<void> send_over_mqtt(client_type& client, const std::string& message) {
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
auto&& [pec, prc, puback_props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", message,
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
use_nothrow_awaitable
);
co_await client.async_disconnect(use_nothrow_awaitable);
}
int main() {
boost::asio::io_context ioc;
co_spawn(ioc, [&ioc]() -> boost::asio::awaitable<void> {
// Construct the Client.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
// Construct the timer.
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
using namespace boost::asio::experimental::awaitable_operators;
auto res = co_await (
send_over_mqtt(client, "Hello world!") ||
timer.async_wait(use_nothrow_awaitable)
);
// The timer expired first. The client is cancelled.
if (res.index() == 1)
std::cout << "Send over MQTT timed out!" << std::endl;
// send_over_mqtt completed first. The timer is cancelled.
else
std::cout << "Send over MQTT completed!" << std::endl;
}, boost::asio::detached);
ioc.run();
}
#endif
//]

View File

@ -0,0 +1,70 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[timeout_with_parallel_group
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
// Construct the Client.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
// Construct the timer.
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// Subscribe to a Topic.
client.async_subscribe(
{ "<your-mqtt-topic>" }, async_mqtt5::subscribe_props {},
[](async_mqtt5::error_code ec, std::vector<async_mqtt5::reason_code> rcs, async_mqtt5::suback_props) {
std::cout << "[subscribe ec]: " << ec.message() << std::endl;
std::cout << "[subscribe rc]: " << rcs[0].message() << std::endl;
}
);
// Create a parallel group to wait up to 5 seconds to receive a message
// using client.async_receive(...).
boost::asio::experimental::make_parallel_group(
timer.async_wait(boost::asio::deferred),
client.async_receive(boost::asio::deferred)
).async_wait(
boost::asio::experimental::wait_for_one(),
[&client](
std::array<std::size_t, 2> ord, // Completion order
async_mqtt5::error_code /* timer_ec */, // timer.async_wait(...) handler signature
// client.async_receive(...) handler signature
async_mqtt5::error_code receive_ec,
std::string topic, std::string payload, async_mqtt5::publish_props /* props */
) {
if (ord[0] == 1) {
std::cout << "Received a message!" << std::endl;
std::cout << "[receive ec]: " << receive_ec.message() << std::endl;
std::cout << "[receive topic]: " << topic << std::endl;
std::cout << "[receive payload]: " << payload << std::endl;
}
else
std::cout << "Timed out! Did not receive a message within 5 seconds." << std::endl;
client.cancel();
}
);
ioc.run();
}
//]

View File

@ -1,172 +0,0 @@
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
namespace asio = boost::asio;
void publish_qos0_websocket_tcp() {
std::cout << "[Test-publish-qos0-websocket-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ip::tcp::socket
>;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-qos0-websocket-tcp", "", "")
.brokers("emqtt.mireo.local/mqtt", 8083)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_most_once>(
"test/mqtt-test", "hello world with qos0!",
retain_e::no, publish_props{},
[&c](error_code ec) {
std::cout << "error_code: " << ec.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void publish_qos1_websocket_tcp() {
std::cout << "[Test-publish-qos1-websocket-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ip::tcp::socket
>;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-qos1-websocket-tcp", "", "")
.brokers("emqtt.mireo.local/mqtt", 8083)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_least_once>(
"test/mqtt-test", "hello world with qos1!",
async_mqtt5::retain_e::no, publish_props{},
[&c](error_code ec, reason_code rc, puback_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void publish_qos2_websocket_tcp() {
std::cout << "[Test-publish-qos2-websocket-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ip::tcp::socket
>;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-qos2-websocket-tcp", "", "")
.brokers("emqtt.mireo.local/mqtt", 8083)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::exactly_once>(
"test/mqtt-test", "hello world with qos2!",
retain_e::no, publish_props{},
[&c](error_code ec, reason_code rc, pubcomp_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void subscribe_and_receive_websocket_tcp(int num_receive) {
std::cout << "[Test-subscribe-and-receive-websocket-tcp]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ip::tcp::socket
>;
using client_type = mqtt_client<stream_type>;
client_type c(ioc);
c.credentials("test-subscriber-websocket-tcp", "", "")
.brokers("emqtt.mireo.local/mqtt", 8083)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
std::vector<subscribe_topic> topics;
topics.push_back(subscribe_topic{
"test/mqtt-test", {
qos_e::exactly_once,
no_local_e::no,
retain_as_published_e::retain,
retain_handling_e::send
}
});
c.async_subscribe(
topics, subscribe_props{},
[](error_code ec, std::vector<reason_code> codes, suback_props) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "subscribe error_code: " << ec.message() << std::endl;
std::cout << "subscribe reason_code: " << codes[0].message() << std::endl;
}
);
for (auto i = 0; i < num_receive; i++) {
c.async_receive(
[&c, i, num_receive] (
error_code ec, std::string topic,
std::string payload, publish_props
) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "message " << i + 1 << "/" << num_receive << std::endl;
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "topic: " << topic << std::endl;
std::cout << "payload: " << payload << std::endl;
if (i == num_receive - 1)
c.async_disconnect(asio::detached);
}
);
}
ioc.run();
return;
}
void run_websocket_tcp_examples() {
publish_qos0_websocket_tcp();
publish_qos1_websocket_tcp();
publish_qos2_websocket_tcp();
subscribe_and_receive_websocket_tcp(1);
}

View File

@ -1,247 +0,0 @@
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
namespace asio = boost::asio;
namespace boost::beast::websocket {
template <typename TeardownHandler>
void async_teardown(
boost::beast::role_type /* role */,
asio::ssl::stream<asio::ip::tcp::socket>& stream,
TeardownHandler&& handler
) {
return stream.async_shutdown(std::forward<TeardownHandler>(handler));
}
} // end namespace boost::beast::websocket
namespace async_mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
asio::ssl::context& /* ctx */,
asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
constexpr char mireo_ca[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDUTCCAjmgAwIBAgIUAzV59EhZA5MXluHNqRi9cBP0x9swDQYJKoZIhvcNAQEL\n"
"BQAwGDEWMBQGA1UEAwwNTWlyZW8gUm9vdCBDQTAeFw0yMjA0MDcxMzM1MjlaFw0z\n"
"MjA0MDQxMzM1MjlaMBgxFjAUBgNVBAMMDU1pcmVvIFJvb3QgQ0EwggEiMA0GCSqG\n"
"SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCin/qsHpdxT3iW0SEHhAcTfESyQcfwGtJE\n"
"jcRrGEj36X6eahyY4AF+4Mlz2vWFeW52ayGXpQKn/z4tChdN80txdY77YmEX7XE0\n"
"HHZYY6toNq/+mNX9h2HvB0GW+8+E0YfNN/HloTxDo3RT8+IovY9OSXt44vY4YtQK\n"
"JbvZIm2Q8Iuv3vfNR05uFa4HcNqFhELh10jss0xG/54Y2NvB6xdKOZ8LRQuIX+Fu\n"
"QRzMiqRFQPUJzWxbKF5I/MFiKWmAG0QNPDnlb8XtPmFTFCWY9X96wOpQOczrxT2+\n"
"+vnTxPA3aTAkz7M4yUuocZQqTlbdfdGOSAENXavewdMCyy5bQsSLAgMBAAGjgZIw\n"
"gY8wHQYDVR0OBBYEFLdUGYfJRf9mbM/fTav9U2vFI+TRMFMGA1UdIwRMMEqAFLdU\n"
"GYfJRf9mbM/fTav9U2vFI+TRoRykGjAYMRYwFAYDVQQDDA1NaXJlbyBSb290IENB\n"
"ghQDNXn0SFkDkxeW4c2pGL1wE/TH2zAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB\n"
"BjANBgkqhkiG9w0BAQsFAAOCAQEAHm5d4YUP8BYcks10UCdswLtxbMUN99fNbnYo\n"
"RMxx4EapwhEZFSNbIZvf1INJd5Po+hH5jteBeFVP+4zKqrhg3I8pjdC4josHmrhS\n"
"28OjOFWp6xNJC43BHnLpc84bH0+XIEBbk7YA6H3GjpsZ7aJkhj/JPjjNq7bmyYN7\n"
"1I9RK4PtIrNtUFbSsHZCZhf8Amtl8PrpktITECjfqCq+8uOAqP4McTIQ1JKwYy6f\n"
"O6iu0eybJCFhWYENTUQyPi1VtEwOpWNLzaXBYdj69Xg8wA/J9RZIoqXWvtHv4rPF\n"
"HGudMEIVB3y2vVLmujvQCqYPZWwbgpy5mN3F4uBNuZhTIwWRFg==\n"
"-----END CERTIFICATE-----\n"
;
void publish_qos0_websocket_tls() {
std::cout << "[Test-publish-qos0-websocket-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ssl::stream<asio::ip::tcp::socket>
>;
error_code ec;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-qos0-websocket-tls", "", "")
.brokers("emqtt.mireo.local/mqtt", 8884)
.will({ "test/mqtt-test", "Client disconnected!", async_mqtt5::qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_most_once>(
"test/mqtt-test", "hello world with qos0!",
retain_e::no, publish_props{},
[&c](error_code ec) {
std::cout << "error_code: " << ec.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void publish_qos1_websocket_tls() {
std::cout << "[Test-publish-qos1-websocket-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ssl::stream<asio::ip::tcp::socket>
>;
error_code ec;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-qos1-websocket-tls", "", "")
.brokers("emqtt.mireo.local/mqtt", 8884)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::at_least_once>(
"test/mqtt-test", "hello world with qos1!",
retain_e::no, publish_props{},
[&c](error_code ec, reason_code rc, puback_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void publish_qos2_websocket_tls() {
std::cout << "[Test-publish-qos2-websocket-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ssl::stream<asio::ip::tcp::socket>
>;
error_code ec;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-qos2-websocket-tls", "", "")
.brokers("emqtt.mireo.local/mqtt", 8884)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
c.async_publish<qos_e::exactly_once>(
"test/mqtt-test", "hello world with qos2!",
retain_e::no, publish_props{},
[&c](error_code ec, reason_code rc, pubcomp_props) {
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "reason_code: " << rc.message() << std::endl;
c.async_disconnect(asio::detached);
}
);
ioc.run();
return;
}
void subscribe_and_receive_websocket_tls(int num_receive) {
std::cout << "[Test-subscribe-and-receive-websocket-tls]" << std::endl;
using namespace async_mqtt5;
asio::io_context ioc;
using stream_type = boost::beast::websocket::stream<
asio::ssl::stream<asio::ip::tcp::socket>
>;
error_code ec;
asio::ssl::context tls_context(asio::ssl::context::tls_client);
tls_context.add_certificate_authority(asio::buffer(mireo_ca), ec);
tls_context.set_verify_mode(asio::ssl::verify_peer);
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
client_type c(ioc, std::move(tls_context));
c.credentials("test-subscriber-websocket-tls", "", "")
.brokers("emqtt.mireo.local/mqtt", 8884)
.will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once })
.async_run(asio::detached);
std::vector<subscribe_topic> topics;
topics.push_back(subscribe_topic{
"test/mqtt-test", {
qos_e::exactly_once,
no_local_e::no,
retain_as_published_e::retain,
retain_handling_e::send
}
});
c.async_subscribe(
topics, subscribe_props{},
[](error_code ec, std::vector<reason_code> codes, suback_props) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "subscribe error_code: " << ec.message() << std::endl;
std::cout << "subscribe reason_code: " << codes[0].message() << std::endl;
}
);
for (auto i = 0; i < num_receive; i++) {
c.async_receive(
[&c, i, num_receive] (
error_code ec, std::string topic,
std::string payload, publish_props
) {
if (ec == asio::error::operation_aborted)
return;
std::cout << "message " << i + 1 << "/" << num_receive << std::endl;
std::cout << "error_code: " << ec.message() << std::endl;
std::cout << "topic: " << topic << std::endl;
std::cout << "payload: " << payload << std::endl;
if (i == num_receive - 1)
c.async_disconnect(asio::detached);
}
);
}
ioc.run();
return;
}
void run_websocket_tls_examples() {
publish_qos0_websocket_tls();
publish_qos1_websocket_tls();
publish_qos2_websocket_tls();
subscribe_and_receive_websocket_tls(1);
}

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_HPP
#define ASYNC_MQTT5_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ANY_AUTHENTICATOR
#define ASYNC_MQTT5_ANY_AUTHENTICATOR

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASYNC_MUTEX_HPP
#define ASYNC_MQTT5_ASYNC_MUTEX_HPP
@ -34,11 +41,10 @@ private:
Handler _handler;
public:
tracked_op(Handler&& h, const Executor& ex) :
_executor(tracking_executor(h, ex)),
_handler(std::move(h))
_executor(tracking_executor(h, ex)), _handler(std::move(h))
{}
tracked_op(tracked_op&&) noexcept = default;
tracked_op(tracked_op&&) = default;
tracked_op(const tracked_op&) = delete;
using executor_type = tracking_type<Handler, Executor>;

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASYNC_TRAITS_HPP
#define ASYNC_MQTT5_ASYNC_TRAITS_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
#define ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
@ -31,7 +38,7 @@ public:
)
{}
cancellable_handler(cancellable_handler&& other) noexcept = default;
cancellable_handler(cancellable_handler&&) = default;
cancellable_handler(const cancellable_handler&) = delete;
using executor_type = tracking_type<Handler, Executor>;

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CHANNEL_TRAITS_HPP
#define ASYNC_MQTT5_CHANNEL_TRAITS_HPP

View File

@ -1,6 +1,14 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CONTROL_PACKET_HPP
#define ASYNC_MQTT5_CONTROL_PACKET_HPP
#include <algorithm>
#include <vector>
#include <boost/smart_ptr/allocate_unique.hpp>

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_INTERNAL_TYPES_HPP
#define ASYNC_MQTT5_INTERNAL_TYPES_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_RING_BUFFER_HPP
#define ASYNC_MQTT5_RING_BUFFER_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_SPINLOCK_HPP
#define ASYNC_MQTT5_SPINLOCK_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_TOPIC_VALIDATION_HPP
#define ASYNC_MQTT5_TOPIC_VALIDATION_HPP
@ -10,7 +17,7 @@ namespace async_mqtt5::detail {
static constexpr uint32_t min_subscription_identifier = 1;
static constexpr uint32_t max_subscription_identifier = 268'435'455;
static constexpr std::string_view shared_sub_id = "$share/";
static constexpr std::string_view shared_sub_prefix = "$share/";
inline bool is_utf8_no_wildcard(validation_result result) {
return result == validation_result::valid;
@ -28,7 +35,11 @@ inline validation_result validate_topic_name(std::string_view str) {
return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard);
}
inline validation_result validate_share_name(std::string_view str) {
inline validation_result validate_topic_alias_name(std::string_view str) {
return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard);
}
inline validation_result validate_shared_topic_name(std::string_view str) {
return validate_impl(str, is_not_empty, is_utf8_no_wildcard);
}
@ -80,17 +91,17 @@ inline validation_result validate_shared_topic_filter(
if (!is_valid_topic_size(str.size()))
return validation_result::invalid;
if (str.compare(0, shared_sub_id.size(), shared_sub_id) != 0)
if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0)
return validation_result::invalid;
str.remove_prefix(shared_sub_id.size());
str.remove_prefix(shared_sub_prefix.size());
size_t share_name_end = str.find_first_of('/');
if (share_name_end == std::string::npos)
return validation_result::invalid;
validation_result result;
result = validate_share_name(str.substr(0, share_name_end));
result = validate_shared_topic_name(str.substr(0, share_name_end));
if (result != validation_result::valid)
return validation_result::invalid;

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_UTF8_MQTT_HPP
#define ASYNC_MQTT5_UTF8_MQTT_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ERROR_HPP
#define ASYNC_MQTT5_ERROR_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASSEMBLE_OP_HPP
#define ASYNC_MQTT5_ASSEMBLE_OP_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASYNC_SENDER_HPP
#define ASYNC_MQTT5_ASYNC_SENDER_HPP
@ -40,7 +47,7 @@ public:
}
asio::const_buffer buffer() const {
return _buffer;
return _buffer;
}
void complete(error_code ec) {
@ -54,6 +61,10 @@ public:
);
}
bool empty() const {
return !_handler;
}
auto get_executor() {
return asio::get_associated_executor(_handler);
}
@ -235,29 +246,24 @@ private:
write_queue = std::move(_write_queue);
}
else {
auto throttled_ptr = std::stable_partition(
_write_queue.begin(), _write_queue.end(),
[](const auto& op) { return !op.throttled(); }
);
uint16_t dist = static_cast<uint16_t>(
std::distance(throttled_ptr, _write_queue.end())
);
uint16_t throttled_num = std::min(dist, _quota);
_quota -= throttled_num;
throttled_ptr += throttled_num;
for (write_req& req : _write_queue)
if (!req.throttled())
write_queue.push_back(std::move(req));
else if (_quota > 0) {
--_quota;
write_queue.push_back(std::move(req));
}
if (throttled_ptr == _write_queue.begin()) {
if (write_queue.empty()) {
_write_in_progress = false;
return;
}
write_queue.insert(
write_queue.end(),
std::make_move_iterator(_write_queue.begin()),
std::make_move_iterator(throttled_ptr)
auto it = std::remove_if(
_write_queue.begin(), _write_queue.end(),
[](const write_req& req) { return req.empty(); }
);
_write_queue.erase(_write_queue.begin(), throttled_ptr);
_write_queue.erase(it, _write_queue.end());
}
std::vector<asio::const_buffer> buffers;

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
#define ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CLIENT_SERVICE_HPP
#define ASYNC_MQTT5_CLIENT_SERVICE_HPP
@ -223,10 +230,10 @@ private:
template <typename ClientService, typename Handler>
friend class assemble_op;
template <typename ClientService>
template <typename ClientService, typename Executor>
friend class ping_op;
template <typename ClientService>
template <typename ClientService, typename Executor>
friend class sentry_op;
template <typename ClientService>
@ -369,7 +376,6 @@ public:
template <typename Handler>
void run(Handler&& handler) {
_executor = asio::get_associated_executor(handler, _executor);
_run_handler = std::move(handler);
auto slot = asio::get_associated_cancellation_slot(_run_handler);
if (slot.is_connected()) {

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_BASE_DECODERS_HPP
#define ASYNC_MQTT5_BASE_DECODERS_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_BASE_ENCODERS_HPP
#define ASYNC_MQTT5_BASE_ENCODERS_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_MESSAGE_DECODERS_HPP
#define ASYNC_MQTT5_MESSAGE_DECODERS_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
#define ASYNC_MQTT5_MESSAGE_ENCODERS_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_TRAITS_HPP
#define ASYNC_MQTT5_TRAITS_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CONNECT_OP_HPP
#define ASYNC_MQTT5_CONNECT_OP_HPP
@ -327,9 +334,8 @@ public:
if (!rc.has_value()) // reason code not allowed in CONNACK
return complete(client::error::malformed_packet);
auto ec = to_asio_error(*rc);
if (ec)
return complete(ec);
if (*rc)
return complete(asio::error::try_again);
if (_ctx.co_props[prop::authentication_method].has_value())
return _ctx.authenticator.async_auth(
@ -425,20 +431,6 @@ private:
_cancellation_state.slot().clear();
std::move(_handler)(ec);
}
static error_code to_asio_error(reason_code rc) {
using namespace boost::asio::error;
using namespace reason_codes;
if (rc == success)
return {};
if (rc == unspecified_error || rc == server_unavailable ||
rc == server_busy || rc == connection_rate_exceeded)
return connection_refused;
return access_denied;
}
};

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_DISCONNECT_OP_HPP
#define ASYNC_MQTT5_DISCONNECT_OP_HPP
@ -45,8 +52,7 @@ public:
const std::shared_ptr<client_service>& svc_ptr,
DisconnectContext&& context, Handler&& handler
) :
_svc_ptr(svc_ptr),
_context(std::move(context)),
_svc_ptr(svc_ptr), _context(std::move(context)),
_handler(std::move(handler), _svc_ptr->get_executor())
{
auto slot = asio::get_associated_cancellation_slot(_handler);
@ -56,7 +62,7 @@ public:
});
}
disconnect_op(disconnect_op&&) noexcept = default;
disconnect_op(disconnect_op&&) = default;
disconnect_op(const disconnect_op&) = delete;
using executor_type = asio::associated_executor_t<handler_type>;
@ -185,7 +191,7 @@ public:
_handler(std::move(handler), _svc_ptr->get_executor())
{}
terminal_disconnect_op(terminal_disconnect_op&&) noexcept = default;
terminal_disconnect_op(terminal_disconnect_op&&) = default;
terminal_disconnect_op(const terminal_disconnect_op&) = delete;
using executor_type = asio::associated_executor_t<handler_type>;

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ENDPOINTS_HPP
#define ASYNC_MQTT5_ENDPOINTS_HPP
@ -33,7 +40,7 @@ public:
_owner(owner), _handler(std::move(handler))
{}
resolve_op(resolve_op&&) noexcept = default;
resolve_op(resolve_op&&) = default;
resolve_op(const resolve_op&) = delete;
using executor_type = asio::associated_executor_t<handler_type>;
@ -67,7 +74,7 @@ public:
authority_path ap = _owner._servers[_owner._current_host];
_owner._connect_timer.expires_from_now(std::chrono::seconds(5));
_owner._connect_timer.expires_after(std::chrono::seconds(5));
auto timed_resolve = asioex::make_parallel_group(
_owner._resolver.async_resolve(ap.host, ap.port, asio::deferred),

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_PING_OP_HPP
#define ASYNC_MQTT5_PING_OP_HPP
@ -20,20 +27,28 @@ namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
template <typename ClientService, typename Executor>
class ping_op {
public:
using executor_type = Executor;
private:
using client_service = ClientService;
struct on_timer {};
struct on_pingreq {};
std::shared_ptr<client_service> _svc_ptr;
executor_type _executor;
std::unique_ptr<asio::steady_timer> _ping_timer;
asio::cancellation_state _cancellation_state;
public:
ping_op(const std::shared_ptr<client_service>& svc_ptr) :
_svc_ptr(svc_ptr),
_ping_timer(new asio::steady_timer(svc_ptr->get_executor())),
ping_op(
const std::shared_ptr<client_service>& svc_ptr,
const executor_type& ex
) :
_svc_ptr(svc_ptr), _executor(ex),
_ping_timer(new asio::steady_timer(_svc_ptr->get_executor())),
_cancellation_state(
svc_ptr->_cancel_ping.slot(),
asio::enable_total_cancellation {},
@ -44,9 +59,8 @@ public:
ping_op(ping_op&&) noexcept = default;
ping_op(const ping_op&) = delete;
using executor_type = typename client_service::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
return _executor;
}
using allocator_type = asio::recycling_allocator<void>;
@ -60,7 +74,7 @@ public:
}
void perform() {
_ping_timer->expires_from_now(compute_wait_time());
_ping_timer->expires_after(compute_wait_time());
_ping_timer->async_wait(
asio::prepend(std::move(*this), on_timer {})
);

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_PUBLISH_REC_OP_HPP
#define ASYNC_MQTT5_PUBLISH_REC_OP_HPP

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
#define ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
@ -79,7 +86,7 @@ public:
});
}
publish_send_op(publish_send_op&&) noexcept = default;
publish_send_op(publish_send_op&& other) = default;
publish_send_op(const publish_send_op&) = delete;
using executor_type = asio::associated_executor_t<handler_type>;
@ -332,7 +339,12 @@ private:
constexpr uint8_t default_maximum_qos = 2;
constexpr uint8_t default_payload_format_ind = 0;
if (validate_topic_name(topic) != validation_result::valid)
auto topic_name_valid = props[prop::topic_alias].has_value() ?
validate_topic_alias_name(topic) == validation_result::valid :
validate_topic_name(topic) == validation_result::valid
;
if (!topic_name_valid)
return client::error::invalid_topic;
auto max_qos = _svc_ptr->connack_property(prop::maximum_qos)

View File

@ -1,9 +1,17 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_RE_AUTH_OP_hpp
#define ASYNC_MQTT5_RE_AUTH_OP_hpp
#include <boost/asio/detached.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/any_authenticator.hpp>

View File

@ -1,3 +1,10 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_READ_MESSAGE_OP_HPP
#define ASYNC_MQTT5_READ_MESSAGE_OP_HPP
@ -20,26 +27,31 @@ namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
template <typename ClientService, typename Executor>
class read_message_op {
public:
using executor_type = Executor;
private:
using client_service = ClientService;
struct on_message {};
struct on_disconnect {};
std::shared_ptr<client_service> _svc_ptr;
executor_type _executor;
public:
read_message_op(
const std::shared_ptr<client_service>& svc_ptr
const std::shared_ptr<client_service>& svc_ptr,
const executor_type& ex
) :
_svc_ptr(svc_ptr)
_svc_ptr(svc_ptr), _executor(ex)
{}
read_message_op(read_message_op&&) noexcept = default;
read_message_op(const read_message_op&) = delete;
using executor_type = typename client_service::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
return _executor;
}
using allocator_type = asio::recycling_allocator<void>;

Some files were not shown because too many files have changed in this diff Show More