Commit 6736e5e07e75823186de581eea5fd0c3e4df7cbb
1 parent
e79fa386
feat: reduce memory footprint for subscriptions significantly
This is a breaking API change. The whole interface of this library now uses "std::string_view" instead of "std::string" / "std::string &". In result, we can now promise to only copy data once thoughout the library. For subscriptions, this is a copy once to read the data from the socket into a buffer. For publish, this is a copy once to write the data in a buffer to send over the socket. For publish, this doesn't change the memory footprint, as because "std::string &" was already taking care of this. For subscriptions however, it reduces the memory usage by a factor of three. And as result it helps with the throughput.
Showing
8 changed files
with
245 additions
and
140 deletions
README.md
| 1 | # TrueMQTT - A modern C++ MQTT Client library | 1 | # TrueMQTT - A modern C++ MQTT Client library |
| 2 | 2 | ||
| 3 | This project is currently a Work In Progress. | 3 | This project is currently a Work In Progress. |
| 4 | -Although the basics are functional, it is untested. | 4 | + |
| 5 | +All basic functionality is in there, but it is lacking some QoL functionalities, and it has not really been hardened / battle-tested yet. | ||
| 5 | 6 | ||
| 6 | ## Development | 7 | ## Development |
| 7 | 8 | ||
| 8 | ```bash | 9 | ```bash |
| 9 | mkdir build | 10 | mkdir build |
| 10 | cd build | 11 | cd build |
| 11 | -cmake .. -DBUILD_SHARED_LIBS=ON -DMIN_LOGGER_LEVEL=TRACE | 12 | +cmake .. -DBUILD_SHARED_LIBS=ON -DMIN_LOGGER_LEVEL=INFO |
| 12 | make -j$(nproc) | 13 | make -j$(nproc) |
| 13 | 14 | ||
| 14 | example/pubsub/truemqtt_pubsub | 15 | example/pubsub/truemqtt_pubsub |
| 15 | ``` | 16 | ``` |
| 17 | + | ||
| 18 | +## Design choices | ||
| 19 | + | ||
| 20 | +### MQTT v3 only | ||
| 21 | + | ||
| 22 | +Although this is a contested choice, for now the library only supports MQTT v3. | ||
| 23 | +There is added value in MQTT v5, but it comes with extra overhead, both in performance and memory. | ||
| 24 | + | ||
| 25 | +This library aims to supply an interface for the more common way of using MQTT, which is a simple publish / subscribe interface. | ||
| 26 | + | ||
| 27 | +In the future this might change, because, as said, MQTT v5 has solid additions, that might be worth delving in it. | ||
| 28 | + | ||
| 29 | +### Copy-once | ||
| 30 | + | ||
| 31 | +A packet that is received from a broker, is only copied once in memory (from `recv()` to an internal buffer). | ||
| 32 | +All subscription callbacks get a `std::string_view` which is directly in this buffer. | ||
| 33 | + | ||
| 34 | +This way, the library only needs to allocate memory once, heavily reducing the memory footprint. | ||
| 35 | +This also means the library can handle big payloads without issue. | ||
| 36 | + | ||
| 37 | +For publishing a similar approach is taken, and the topic / message is only copied once in an internal buffer. | ||
| 38 | +The only exception here is when the client isn't connected to the broker (yet). | ||
| 39 | +In this scenario, a copy of topic / message is made, and there will be two allocations for both, instead of one. | ||
| 40 | + | ||
| 41 | +Either way, this makes this library highly efficient in terms of memory usage. | ||
| 42 | + | ||
| 43 | +The drawback is that you have to be careful with your callbacks. | ||
| 44 | +You always receive a `std::string_view`, that is only valid within that callback. | ||
| 45 | +As soon as the callback returns, the memory becomes invalid. | ||
| 46 | + | ||
| 47 | +This means that if you need to keep the topic and/or message around, you need to make a copy. | ||
| 48 | + | ||
| 49 | +### QoS 0 | ||
| 50 | + | ||
| 51 | +This library only supports QoS 0. | ||
| 52 | +This is mainly because that is the only QoS I have ever used since using MQTT. | ||
| 53 | + | ||
| 54 | +The problem with other QoSes is that is is mostly pointless up till the point it is useful. | ||
| 55 | +MQTT uses TCP, and as such, delivery over the socket is guaranteed if both sides are still alive. | ||
| 56 | +In other words, QoS 1 doesn't add any guarantees in the normal situation, where lines / brokers aren't saturated. | ||
| 57 | +When it does get saturated, QoS 1 becomes useful. | ||
| 58 | + | ||
| 59 | +But, there is a trade-off here. | ||
| 60 | +If you always do QoS 1 for the cases where the line does get saturated, you put more pressure on the line in all cases, which results in a line that is saturated more quickly. | ||
| 61 | +And in reality, it is very hard to recover from such scenarios anyway. | ||
| 62 | + | ||
| 63 | +MQTT 5 corrects this situation, by a bit of a cheat. | ||
| 64 | +If you publish with QoS 1, but the TCP connection was working as expected, it in fact handles it as a QoS 0 request. | ||
| 65 | + | ||
| 66 | +For this reason, this library only supports QoS 0. | ||
| 67 | +As added benefit, it makes for easier code, which is less like to have bugs / problems. |
example/pubsub/main.cpp
| @@ -14,10 +14,10 @@ int main() | @@ -14,10 +14,10 @@ int main() | ||
| 14 | // Create a connection to the local broker. | 14 | // Create a connection to the local broker. |
| 15 | TrueMQTT::Client client("localhost", 1883, "test"); | 15 | TrueMQTT::Client client("localhost", 1883, "test"); |
| 16 | 16 | ||
| 17 | - client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string message) | 17 | + client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string_view message) |
| 18 | { std::cout << "Log " << level << ": " << message << std::endl; }); | 18 | { std::cout << "Log " << level << ": " << message << std::endl; }); |
| 19 | client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10); | 19 | client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10); |
| 20 | - client.setErrorCallback([](TrueMQTT::Client::Error error, std::string message) | 20 | + client.setErrorCallback([](TrueMQTT::Client::Error error, std::string_view message) |
| 21 | { std::cout << "Error " << error << ": " << message << std::endl; }); | 21 | { std::cout << "Error " << error << ": " << message << std::endl; }); |
| 22 | client.setLastWill("example/pubsub/lastwill", "example pubsub finished", true); | 22 | client.setLastWill("example/pubsub/lastwill", "example pubsub finished", true); |
| 23 | 23 | ||
| @@ -26,23 +26,23 @@ int main() | @@ -26,23 +26,23 @@ int main() | ||
| 26 | int stop = 0; | 26 | int stop = 0; |
| 27 | 27 | ||
| 28 | // Subscribe to the topic we will be publishing under in a bit. | 28 | // Subscribe to the topic we will be publishing under in a bit. |
| 29 | - client.subscribe("example/pubsub/test/subtest", [&stop](const std::string topic, const std::string payload) | 29 | + client.subscribe("example/pubsub/test/subtest", [&stop](const std::string_view topic, const std::string_view payload) |
| 30 | { | 30 | { |
| 31 | std::cout << "Received message on exact topic " << topic << ": " << payload << std::endl; | 31 | std::cout << "Received message on exact topic " << topic << ": " << payload << std::endl; |
| 32 | stop++; }); | 32 | stop++; }); |
| 33 | - client.subscribe("example/pubsub/test/subtest", [&stop](const std::string topic, const std::string payload) | 33 | + client.subscribe("example/pubsub/test/subtest", [&stop](const std::string_view topic, const std::string_view payload) |
| 34 | { | 34 | { |
| 35 | - std::cout << "Received message on exact topic " << topic << ": " << payload << std::endl; | 35 | + std::cout << "Received message on exact topic " << topic << " again: " << payload << std::endl; |
| 36 | stop++; }); | 36 | stop++; }); |
| 37 | - client.subscribe("example/pubsub/+/subtest", [&stop](const std::string topic, const std::string payload) | 37 | + client.subscribe("example/pubsub/+/subtest", [&stop](const std::string_view topic, const std::string_view payload) |
| 38 | { | 38 | { |
| 39 | std::cout << "Received message on single wildcard topic " << topic << ": " << payload << std::endl; | 39 | std::cout << "Received message on single wildcard topic " << topic << ": " << payload << std::endl; |
| 40 | stop++; }); | 40 | stop++; }); |
| 41 | - client.subscribe("example/pubsub/test/#", [&stop](const std::string topic, const std::string payload) | 41 | + client.subscribe("example/pubsub/test/#", [&stop](const std::string_view topic, const std::string_view payload) |
| 42 | { | 42 | { |
| 43 | std::cout << "Received message on multi wildcard topic " << topic << ": " << payload << std::endl; | 43 | std::cout << "Received message on multi wildcard topic " << topic << ": " << payload << std::endl; |
| 44 | stop++; }); | 44 | stop++; }); |
| 45 | - client.subscribe("example/pubsub/test/+", [&stop](const std::string topic, const std::string payload) | 45 | + client.subscribe("example/pubsub/test/+", [&stop](const std::string_view topic, const std::string_view payload) |
| 46 | { | 46 | { |
| 47 | /* Never actually called, as we unsubscribe a bit later */ }); | 47 | /* Never actually called, as we unsubscribe a bit later */ }); |
| 48 | 48 |
example/stress/main.cpp
| @@ -14,10 +14,10 @@ int main() | @@ -14,10 +14,10 @@ int main() | ||
| 14 | // Create a connection to the local broker. | 14 | // Create a connection to the local broker. |
| 15 | TrueMQTT::Client client("localhost", 1883, "test"); | 15 | TrueMQTT::Client client("localhost", 1883, "test"); |
| 16 | 16 | ||
| 17 | - client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string message) | 17 | + client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string_view message) |
| 18 | { std::cout << "Log " << level << ": " << message << std::endl; }); | 18 | { std::cout << "Log " << level << ": " << message << std::endl; }); |
| 19 | client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 100); | 19 | client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 100); |
| 20 | - client.setErrorCallback([](TrueMQTT::Client::Error error, std::string message) | 20 | + client.setErrorCallback([](TrueMQTT::Client::Error error, std::string_view message) |
| 21 | { std::cout << "Error " << error << ": " << message << std::endl; }); | 21 | { std::cout << "Error " << error << ": " << message << std::endl; }); |
| 22 | client.setLastWill("test/lastwill", "example pubsub finished", true); | 22 | client.setLastWill("test/lastwill", "example pubsub finished", true); |
| 23 | 23 | ||
| @@ -30,11 +30,11 @@ int main() | @@ -30,11 +30,11 @@ int main() | ||
| 30 | int64_t totalLatency = 0; | 30 | int64_t totalLatency = 0; |
| 31 | 31 | ||
| 32 | // Subscribe to the topic we are going to stress test. | 32 | // Subscribe to the topic we are going to stress test. |
| 33 | - client.subscribe("example/stress/+", [&received, &totalLatency](const std::string topic, const std::string payload) | 33 | + client.subscribe("example/stress/+", [&received, &totalLatency](const std::string_view topic, const std::string_view payload) |
| 34 | { | 34 | { |
| 35 | // Calculate the latency. | 35 | // Calculate the latency. |
| 36 | auto now = std::chrono::steady_clock::now(); | 36 | auto now = std::chrono::steady_clock::now(); |
| 37 | - auto then = std::chrono::time_point<std::chrono::steady_clock>(std::chrono::microseconds(std::stoll(payload))); | 37 | + auto then = std::chrono::time_point<std::chrono::steady_clock>(std::chrono::microseconds(std::stoll(std::string(payload)))); |
| 38 | auto latency = std::chrono::duration_cast<std::chrono::microseconds>(now - then).count(); | 38 | auto latency = std::chrono::duration_cast<std::chrono::microseconds>(now - then).count(); |
| 39 | 39 | ||
| 40 | totalLatency += latency; | 40 | totalLatency += latency; |
include/TrueMQTT.h
| @@ -10,6 +10,7 @@ | @@ -10,6 +10,7 @@ | ||
| 10 | #include <chrono> | 10 | #include <chrono> |
| 11 | #include <functional> | 11 | #include <functional> |
| 12 | #include <memory> | 12 | #include <memory> |
| 13 | +#include <string_view> | ||
| 13 | 14 | ||
| 14 | namespace TrueMQTT | 15 | namespace TrueMQTT |
| 15 | { | 16 | { |
| @@ -116,9 +117,9 @@ namespace TrueMQTT | @@ -116,9 +117,9 @@ namespace TrueMQTT | ||
| 116 | * @param connection_backoff_max Maximum time between backoff attempts in seconds. | 117 | * @param connection_backoff_max Maximum time between backoff attempts in seconds. |
| 117 | * @param keep_alive_interval Interval in seconds between keep-alive messages. | 118 | * @param keep_alive_interval Interval in seconds between keep-alive messages. |
| 118 | */ | 119 | */ |
| 119 | - Client(const std::string &host, | 120 | + Client(const std::string_view host, |
| 120 | int port, | 121 | int port, |
| 121 | - const std::string &client_id, | 122 | + const std::string_view client_id, |
| 122 | std::chrono::milliseconds connection_timeout = std::chrono::milliseconds(5000), | 123 | std::chrono::milliseconds connection_timeout = std::chrono::milliseconds(5000), |
| 123 | std::chrono::milliseconds connection_backoff = std::chrono::milliseconds(1000), | 124 | std::chrono::milliseconds connection_backoff = std::chrono::milliseconds(1000), |
| 124 | std::chrono::milliseconds connection_backoff_max = std::chrono::milliseconds(30000), | 125 | std::chrono::milliseconds connection_backoff_max = std::chrono::milliseconds(30000), |
| @@ -142,7 +143,7 @@ namespace TrueMQTT | @@ -142,7 +143,7 @@ namespace TrueMQTT | ||
| 142 | * @note This library doesn't contain a logger, so you need to provide one. | 143 | * @note This library doesn't contain a logger, so you need to provide one. |
| 143 | * If this method is not called, no logging will be done. | 144 | * If this method is not called, no logging will be done. |
| 144 | */ | 145 | */ |
| 145 | - void setLogger(LogLevel log_level, const std::function<void(LogLevel, std::string)> &logger) const; | 146 | + void setLogger(LogLevel log_level, const std::function<void(LogLevel, std::string_view)> &logger) const; |
| 146 | 147 | ||
| 147 | /** | 148 | /** |
| 148 | * @brief Set the last will message on the connection. | 149 | * @brief Set the last will message on the connection. |
| @@ -155,14 +156,14 @@ namespace TrueMQTT | @@ -155,14 +156,14 @@ namespace TrueMQTT | ||
| 155 | * | 156 | * |
| 156 | * @note Cannot be called after \ref connect. | 157 | * @note Cannot be called after \ref connect. |
| 157 | */ | 158 | */ |
| 158 | - void setLastWill(const std::string &topic, const std::string &message, bool retain) const; | 159 | + void setLastWill(const std::string_view topic, const std::string_view message, bool retain) const; |
| 159 | 160 | ||
| 160 | /** | 161 | /** |
| 161 | * @brief Set the error callback, called when any error occurs. | 162 | * @brief Set the error callback, called when any error occurs. |
| 162 | * | 163 | * |
| 163 | * @param callback The callback to call when an error occurs. | 164 | * @param callback The callback to call when an error occurs. |
| 164 | */ | 165 | */ |
| 165 | - void setErrorCallback(const std::function<void(Error, std::string)> &callback) const; | 166 | + void setErrorCallback(const std::function<void(Error, std::string_view)> &callback) const; |
| 166 | 167 | ||
| 167 | /** | 168 | /** |
| 168 | * @brief Set the publish queue to use. | 169 | * @brief Set the publish queue to use. |
| @@ -247,7 +248,7 @@ namespace TrueMQTT | @@ -247,7 +248,7 @@ namespace TrueMQTT | ||
| 247 | * in this case, but it is wise to back off for a while before sending something | 248 | * in this case, but it is wise to back off for a while before sending something |
| 248 | * again. | 249 | * again. |
| 249 | */ | 250 | */ |
| 250 | - bool publish(const std::string &topic, const std::string &message, bool retain) const; | 251 | + bool publish(const std::string_view topic, const std::string_view message, bool retain) const; |
| 251 | 252 | ||
| 252 | /** | 253 | /** |
| 253 | * @brief Subscribe to a topic, and call the callback function when a message arrives. | 254 | * @brief Subscribe to a topic, and call the callback function when a message arrives. |
| @@ -261,6 +262,9 @@ namespace TrueMQTT | @@ -261,6 +262,9 @@ namespace TrueMQTT | ||
| 261 | * @param topic The topic to subscribe to. | 262 | * @param topic The topic to subscribe to. |
| 262 | * @param callback The callback to call when a message arrives on this topic. | 263 | * @param callback The callback to call when a message arrives on this topic. |
| 263 | * | 264 | * |
| 265 | + * @note The callback receives a string_view for topic/message, which is only valid | ||
| 266 | + * for the duration of the callback. If you need to retain the value of longer, | ||
| 267 | + * make sure to copy the content. | ||
| 264 | * @note Subscription can overlap, even on the exact same topic. All callbacks that | 268 | * @note Subscription can overlap, even on the exact same topic. All callbacks that |
| 265 | * match the topic will be called. | 269 | * match the topic will be called. |
| 266 | * @note Depending on the broker, overlapping subscriptions can trigger one or more | 270 | * @note Depending on the broker, overlapping subscriptions can trigger one or more |
| @@ -279,7 +283,7 @@ namespace TrueMQTT | @@ -279,7 +283,7 @@ namespace TrueMQTT | ||
| 279 | * moment the connection to the broker is established, and there are messages in the | 283 | * moment the connection to the broker is established, and there are messages in the |
| 280 | * publish queue and/or subscriptions. | 284 | * publish queue and/or subscriptions. |
| 281 | */ | 285 | */ |
| 282 | - void subscribe(const std::string &topic, const std::function<void(std::string, std::string)> &callback) const; | 286 | + void subscribe(const std::string_view topic, const std::function<void(std::string_view, std::string_view)> &callback) const; |
| 283 | 287 | ||
| 284 | /** | 288 | /** |
| 285 | * @brief Unsubscribe from a topic. | 289 | * @brief Unsubscribe from a topic. |
| @@ -295,7 +299,7 @@ namespace TrueMQTT | @@ -295,7 +299,7 @@ namespace TrueMQTT | ||
| 295 | * moment the connection to the broker is established, and there are messages in the | 299 | * moment the connection to the broker is established, and there are messages in the |
| 296 | * publish queue and/or subscriptions. | 300 | * publish queue and/or subscriptions. |
| 297 | */ | 301 | */ |
| 298 | - void unsubscribe(const std::string &topic) const; | 302 | + void unsubscribe(const std::string_view topic) const; |
| 299 | 303 | ||
| 300 | private: | 304 | private: |
| 301 | // Private implementation | 305 | // Private implementation |
src/Client.cpp
| @@ -13,9 +13,9 @@ | @@ -13,9 +13,9 @@ | ||
| 13 | 13 | ||
| 14 | #include <sstream> | 14 | #include <sstream> |
| 15 | 15 | ||
| 16 | -TrueMQTT::Client::Client(const std::string &host, | 16 | +TrueMQTT::Client::Client(const std::string_view host, |
| 17 | int port, | 17 | int port, |
| 18 | - const std::string &client_id, | 18 | + const std::string_view client_id, |
| 19 | std::chrono::milliseconds connection_timeout, | 19 | std::chrono::milliseconds connection_timeout, |
| 20 | std::chrono::milliseconds connection_backoff, | 20 | std::chrono::milliseconds connection_backoff, |
| 21 | std::chrono::milliseconds connection_backoff_max, | 21 | std::chrono::milliseconds connection_backoff_max, |
| @@ -33,9 +33,9 @@ TrueMQTT::Client::~Client() | @@ -33,9 +33,9 @@ TrueMQTT::Client::~Client() | ||
| 33 | disconnect(); | 33 | disconnect(); |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | -TrueMQTT::Client::Impl::Impl(const std::string &host, | 36 | +TrueMQTT::Client::Impl::Impl(const std::string_view host, |
| 37 | int port, | 37 | int port, |
| 38 | - const std::string &client_id, | 38 | + const std::string_view client_id, |
| 39 | std::chrono::milliseconds connection_timeout, | 39 | std::chrono::milliseconds connection_timeout, |
| 40 | std::chrono::milliseconds connection_backoff, | 40 | std::chrono::milliseconds connection_backoff, |
| 41 | std::chrono::milliseconds connection_backoff_max, | 41 | std::chrono::milliseconds connection_backoff_max, |
| @@ -54,7 +54,7 @@ TrueMQTT::Client::Impl::~Impl() | @@ -54,7 +54,7 @@ TrueMQTT::Client::Impl::~Impl() | ||
| 54 | { | 54 | { |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | -void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function<void(Client::LogLevel, std::string)> &logger) const | 57 | +void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function<void(Client::LogLevel, std::string_view)> &logger) const |
| 58 | { | 58 | { |
| 59 | LOG_TRACE(m_impl, "Setting logger to log level " + std::to_string(log_level)); | 59 | LOG_TRACE(m_impl, "Setting logger to log level " + std::to_string(log_level)); |
| 60 | 60 | ||
| @@ -64,7 +64,7 @@ void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function | @@ -64,7 +64,7 @@ void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function | ||
| 64 | LOG_DEBUG(m_impl, "Log level now on " + std::to_string(m_impl->m_log_level)); | 64 | LOG_DEBUG(m_impl, "Log level now on " + std::to_string(m_impl->m_log_level)); |
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | -void TrueMQTT::Client::setLastWill(const std::string &topic, const std::string &message, bool retain) const | 67 | +void TrueMQTT::Client::setLastWill(const std::string_view topic, const std::string_view message, bool retain) const |
| 68 | { | 68 | { |
| 69 | if (m_impl->m_state != Client::Impl::State::DISCONNECTED) | 69 | if (m_impl->m_state != Client::Impl::State::DISCONNECTED) |
| 70 | { | 70 | { |
| @@ -72,14 +72,14 @@ void TrueMQTT::Client::setLastWill(const std::string &topic, const std::string & | @@ -72,14 +72,14 @@ void TrueMQTT::Client::setLastWill(const std::string &topic, const std::string & | ||
| 72 | return; | 72 | return; |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | - LOG_TRACE(m_impl, "Setting last will to topic " + topic + " with message " + message + " and retain " + std::to_string(retain)); | 75 | + LOG_TRACE(m_impl, "Setting last will to topic " + std::string(topic) + " with message " + std::string(message) + " and retain " + std::to_string(retain)); |
| 76 | 76 | ||
| 77 | m_impl->m_last_will_topic = topic; | 77 | m_impl->m_last_will_topic = topic; |
| 78 | m_impl->m_last_will_message = message; | 78 | m_impl->m_last_will_message = message; |
| 79 | m_impl->m_last_will_retain = retain; | 79 | m_impl->m_last_will_retain = retain; |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | -void TrueMQTT::Client::setErrorCallback(const std::function<void(Error, std::string)> &callback) const | 82 | +void TrueMQTT::Client::setErrorCallback(const std::function<void(Error, std::string_view)> &callback) const |
| 83 | { | 83 | { |
| 84 | LOG_TRACE(m_impl, "Setting error callback"); | 84 | LOG_TRACE(m_impl, "Setting error callback"); |
| 85 | 85 | ||
| @@ -144,11 +144,11 @@ void TrueMQTT::Client::disconnect() const | @@ -144,11 +144,11 @@ void TrueMQTT::Client::disconnect() const | ||
| 144 | m_impl->disconnect(); | 144 | m_impl->disconnect(); |
| 145 | } | 145 | } |
| 146 | 146 | ||
| 147 | -bool TrueMQTT::Client::publish(const std::string &topic, const std::string &message, bool retain) const | 147 | +bool TrueMQTT::Client::publish(const std::string_view topic, const std::string_view message, bool retain) const |
| 148 | { | 148 | { |
| 149 | std::scoped_lock lock(m_impl->m_state_mutex); | 149 | std::scoped_lock lock(m_impl->m_state_mutex); |
| 150 | 150 | ||
| 151 | - LOG_DEBUG(m_impl, "Publishing message on topic '" + topic + "': " + message + " (" + (retain ? "retained" : "not retained") + ")"); | 151 | + LOG_DEBUG(m_impl, "Publishing message on topic '" + std::string(topic) + "': " + std::string(message) + " (" + (retain ? "retained" : "not retained") + ")"); |
| 152 | 152 | ||
| 153 | switch (m_impl->m_state) | 153 | switch (m_impl->m_state) |
| 154 | { | 154 | { |
| @@ -164,7 +164,7 @@ bool TrueMQTT::Client::publish(const std::string &topic, const std::string &mess | @@ -164,7 +164,7 @@ bool TrueMQTT::Client::publish(const std::string &topic, const std::string &mess | ||
| 164 | return false; | 164 | return false; |
| 165 | } | 165 | } |
| 166 | 166 | ||
| 167 | -void TrueMQTT::Client::subscribe(const std::string &topic, const std::function<void(std::string, std::string)> &callback) const | 167 | +void TrueMQTT::Client::subscribe(const std::string_view topic, const std::function<void(std::string_view, std::string_view)> &callback) const |
| 168 | { | 168 | { |
| 169 | std::scoped_lock lock(m_impl->m_state_mutex); | 169 | std::scoped_lock lock(m_impl->m_state_mutex); |
| 170 | 170 | ||
| @@ -174,23 +174,44 @@ void TrueMQTT::Client::subscribe(const std::string &topic, const std::function<v | @@ -174,23 +174,44 @@ void TrueMQTT::Client::subscribe(const std::string &topic, const std::function<v | ||
| 174 | return; | 174 | return; |
| 175 | } | 175 | } |
| 176 | 176 | ||
| 177 | - LOG_DEBUG(m_impl, "Subscribing to topic '" + topic + "'"); | 177 | + LOG_DEBUG(m_impl, "Subscribing to topic '" + std::string(topic) + "'"); |
| 178 | 178 | ||
| 179 | - // Split the topic on /, to find each part. | ||
| 180 | - std::string part; | ||
| 181 | - std::stringstream stopic(topic); | ||
| 182 | - std::getline(stopic, part, '/'); | ||
| 183 | - | ||
| 184 | - // Find the root node, and walk down till we find the leaf node. | ||
| 185 | - Client::Impl::SubscriptionPart *subscriptions = &m_impl->m_subscriptions.try_emplace(part).first->second; | ||
| 186 | - while (std::getline(stopic, part, '/')) | 179 | + // Find where in the tree the callback for this subscription should be added. |
| 180 | + Client::Impl::SubscriptionPart *subscriptions = nullptr; | ||
| 181 | + std::string_view topic_search = topic; | ||
| 182 | + while (true) | ||
| 187 | { | 183 | { |
| 188 | - subscriptions = &subscriptions->children.try_emplace(part).first->second; | 184 | + std::string_view part = topic_search; |
| 185 | + | ||
| 186 | + // Find the next part of the topic. | ||
| 187 | + auto pos = topic_search.find('/'); | ||
| 188 | + if (pos != std::string_view::npos) | ||
| 189 | + { | ||
| 190 | + part = topic_search.substr(0, pos); | ||
| 191 | + topic_search.remove_prefix(pos + 1); | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + // Find the next subscription in the tree. | ||
| 195 | + if (subscriptions == nullptr) | ||
| 196 | + { | ||
| 197 | + subscriptions = &m_impl->m_subscriptions.try_emplace(std::string(part)).first->second; | ||
| 198 | + } | ||
| 199 | + else | ||
| 200 | + { | ||
| 201 | + subscriptions = &subscriptions->children.try_emplace(std::string(part)).first->second; | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + // If this was the last element, we're done. | ||
| 205 | + if (pos == std::string_view::npos) | ||
| 206 | + { | ||
| 207 | + break; | ||
| 208 | + } | ||
| 189 | } | 209 | } |
| 210 | + | ||
| 190 | // Add the callback to the leaf node. | 211 | // Add the callback to the leaf node. |
| 191 | subscriptions->callbacks.push_back(callback); | 212 | subscriptions->callbacks.push_back(callback); |
| 192 | 213 | ||
| 193 | - m_impl->m_subscription_topics.insert(topic); | 214 | + m_impl->m_subscription_topics.insert(std::string(topic)); |
| 194 | if (m_impl->m_state == Client::Impl::State::CONNECTED) | 215 | if (m_impl->m_state == Client::Impl::State::CONNECTED) |
| 195 | { | 216 | { |
| 196 | if (!m_impl->sendSubscribe(topic)) | 217 | if (!m_impl->sendSubscribe(topic)) |
| @@ -202,7 +223,7 @@ void TrueMQTT::Client::subscribe(const std::string &topic, const std::function<v | @@ -202,7 +223,7 @@ void TrueMQTT::Client::subscribe(const std::string &topic, const std::function<v | ||
| 202 | } | 223 | } |
| 203 | } | 224 | } |
| 204 | 225 | ||
| 205 | -void TrueMQTT::Client::unsubscribe(const std::string &topic) const | 226 | +void TrueMQTT::Client::unsubscribe(const std::string_view topic) const |
| 206 | { | 227 | { |
| 207 | std::scoped_lock lock(m_impl->m_state_mutex); | 228 | std::scoped_lock lock(m_impl->m_state_mutex); |
| 208 | 229 | ||
| @@ -212,34 +233,63 @@ void TrueMQTT::Client::unsubscribe(const std::string &topic) const | @@ -212,34 +233,63 @@ void TrueMQTT::Client::unsubscribe(const std::string &topic) const | ||
| 212 | return; | 233 | return; |
| 213 | } | 234 | } |
| 214 | 235 | ||
| 215 | - LOG_DEBUG(m_impl, "Unsubscribing from topic '" + topic + "'"); | 236 | + if (m_impl->m_subscription_topics.find(topic) == m_impl->m_subscription_topics.end()) |
| 237 | + { | ||
| 238 | + LOG_ERROR(m_impl, "Cannot unsubscribe from topic '" + std::string(topic) + "' because we are not subscribed to it"); | ||
| 239 | + return; | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + LOG_DEBUG(m_impl, "Unsubscribing from topic '" + std::string(topic) + "'"); | ||
| 216 | 243 | ||
| 217 | - // Split the topic on /, to find each part. | ||
| 218 | - std::string part; | ||
| 219 | - std::stringstream stopic(topic); | ||
| 220 | - std::getline(stopic, part, '/'); | 244 | + std::vector<std::tuple<std::string_view, Client::Impl::SubscriptionPart *>> reverse; |
| 221 | 245 | ||
| 222 | - // Find the root node, and walk down till we find the leaf node. | ||
| 223 | - std::vector<std::tuple<std::string, Client::Impl::SubscriptionPart *>> reverse; | ||
| 224 | - Client::Impl::SubscriptionPart *subscriptions = &m_impl->m_subscriptions[part]; | ||
| 225 | - reverse.emplace_back(part, subscriptions); | ||
| 226 | - while (std::getline(stopic, part, '/')) | 246 | + // Find where in the tree the callback for this subscription should be removed. |
| 247 | + Client::Impl::SubscriptionPart *subscriptions = nullptr; | ||
| 248 | + std::string_view topic_search = topic; | ||
| 249 | + while (true) | ||
| 227 | { | 250 | { |
| 228 | - subscriptions = &subscriptions->children[part]; | 251 | + std::string_view part = topic_search; |
| 252 | + | ||
| 253 | + // Find the next part of the topic. | ||
| 254 | + auto pos = topic_search.find('/'); | ||
| 255 | + if (pos != std::string_view::npos) | ||
| 256 | + { | ||
| 257 | + part = topic_search.substr(0, pos); | ||
| 258 | + topic_search.remove_prefix(pos + 1); | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + // Find the next subscription in the tree. | ||
| 262 | + if (subscriptions == nullptr) | ||
| 263 | + { | ||
| 264 | + subscriptions = &m_impl->m_subscriptions.find(part)->second; | ||
| 265 | + } | ||
| 266 | + else | ||
| 267 | + { | ||
| 268 | + subscriptions = &subscriptions->children.find(part)->second; | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + // Update the reverse lookup. | ||
| 229 | reverse.emplace_back(part, subscriptions); | 272 | reverse.emplace_back(part, subscriptions); |
| 273 | + | ||
| 274 | + // If this was the last element, we're done. | ||
| 275 | + if (pos == std::string_view::npos) | ||
| 276 | + { | ||
| 277 | + break; | ||
| 278 | + } | ||
| 230 | } | 279 | } |
| 280 | + | ||
| 231 | // Clear the callbacks in the leaf node. | 281 | // Clear the callbacks in the leaf node. |
| 232 | subscriptions->callbacks.clear(); | 282 | subscriptions->callbacks.clear(); |
| 233 | 283 | ||
| 234 | // Bookkeeping: remove any empty nodes. | 284 | // Bookkeeping: remove any empty nodes. |
| 235 | // Otherwise we will slowly grow in memory if a user does a lot of unsubscribes | 285 | // Otherwise we will slowly grow in memory if a user does a lot of unsubscribes |
| 236 | // on different topics. | 286 | // on different topics. |
| 237 | - std::string remove_next = ""; | 287 | + std::string_view remove_next = ""; |
| 238 | for (auto it = reverse.rbegin(); it != reverse.rend(); it++) | 288 | for (auto it = reverse.rbegin(); it != reverse.rend(); it++) |
| 239 | { | 289 | { |
| 240 | if (!remove_next.empty()) | 290 | if (!remove_next.empty()) |
| 241 | { | 291 | { |
| 242 | - std::get<1>(*it)->children.erase(remove_next); | 292 | + std::get<1>(*it)->children.erase(std::get<1>(*it)->children.find(remove_next)); |
| 243 | remove_next = ""; | 293 | remove_next = ""; |
| 244 | } | 294 | } |
| 245 | 295 | ||
| @@ -250,10 +300,10 @@ void TrueMQTT::Client::unsubscribe(const std::string &topic) const | @@ -250,10 +300,10 @@ void TrueMQTT::Client::unsubscribe(const std::string &topic) const | ||
| 250 | } | 300 | } |
| 251 | if (!remove_next.empty()) | 301 | if (!remove_next.empty()) |
| 252 | { | 302 | { |
| 253 | - m_impl->m_subscriptions.erase(remove_next); | 303 | + m_impl->m_subscriptions.erase(m_impl->m_subscriptions.find(remove_next)); |
| 254 | } | 304 | } |
| 255 | 305 | ||
| 256 | - m_impl->m_subscription_topics.erase(topic); | 306 | + m_impl->m_subscription_topics.erase(m_impl->m_subscription_topics.find(topic)); |
| 257 | if (m_impl->m_state == Client::Impl::State::CONNECTED) | 307 | if (m_impl->m_state == Client::Impl::State::CONNECTED) |
| 258 | { | 308 | { |
| 259 | if (!m_impl->sendUnsubscribe(topic)) | 309 | if (!m_impl->sendUnsubscribe(topic)) |
| @@ -312,7 +362,7 @@ void TrueMQTT::Client::Impl::connectionStateChange(bool connected) | @@ -312,7 +362,7 @@ void TrueMQTT::Client::Impl::connectionStateChange(bool connected) | ||
| 312 | } | 362 | } |
| 313 | } | 363 | } |
| 314 | 364 | ||
| 315 | -bool TrueMQTT::Client::Impl::toPublishQueue(const std::string &topic, const std::string &message, bool retain) | 365 | +bool TrueMQTT::Client::Impl::toPublishQueue(const std::string_view topic, const std::string_view message, bool retain) |
| 316 | { | 366 | { |
| 317 | if (m_state != Client::Impl::State::CONNECTING) | 367 | if (m_state != Client::Impl::State::CONNECTING) |
| 318 | { | 368 | { |
| @@ -346,26 +396,34 @@ bool TrueMQTT::Client::Impl::toPublishQueue(const std::string &topic, const std: | @@ -346,26 +396,34 @@ bool TrueMQTT::Client::Impl::toPublishQueue(const std::string &topic, const std: | ||
| 346 | return true; | 396 | return true; |
| 347 | } | 397 | } |
| 348 | 398 | ||
| 349 | -void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector<std::function<void(std::string, std::string)>> &matching_callbacks, const std::map<std::string, Client::Impl::SubscriptionPart> &subscriptions, std::deque<std::string> &parts) | 399 | +void TrueMQTT::Client::Impl::findSubscriptionMatch(std::string_view topic, std::string_view message, std::string_view topic_search, const std::map<std::string, Client::Impl::SubscriptionPart, std::less<>> &subscriptions) |
| 350 | { | 400 | { |
| 351 | - // If we reached the end of the topic, do nothing anymore. | ||
| 352 | - if (parts.empty()) | 401 | + std::string_view part = topic_search; |
| 402 | + | ||
| 403 | + // Find the next part of the topic. | ||
| 404 | + auto pos = topic_search.find('/'); | ||
| 405 | + if (pos != std::string_view::npos) | ||
| 353 | { | 406 | { |
| 354 | - return; | 407 | + part = topic_search.substr(0, pos); |
| 408 | + topic_search.remove_prefix(pos + 1); | ||
| 355 | } | 409 | } |
| 356 | 410 | ||
| 357 | - LOG_TRACE(this, "Finding subscription match for part '" + parts.front() + "'"); | ||
| 358 | - | ||
| 359 | // Find the match based on the part. | 411 | // Find the match based on the part. |
| 360 | - auto it = subscriptions.find(parts.front()); | 412 | + auto it = subscriptions.find(part); |
| 361 | if (it != subscriptions.end()) | 413 | if (it != subscriptions.end()) |
| 362 | { | 414 | { |
| 363 | - LOG_TRACE(this, "Found subscription match for part '" + parts.front() + "' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); | 415 | + LOG_TRACE(this, "Found subscription match for part '" + std::string(part) + "' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); |
| 364 | 416 | ||
| 365 | - matching_callbacks.insert(matching_callbacks.end(), it->second.callbacks.begin(), it->second.callbacks.end()); | 417 | + for (const auto &callback : it->second.callbacks) |
| 418 | + { | ||
| 419 | + callback(topic, message); | ||
| 420 | + } | ||
| 366 | 421 | ||
| 367 | - std::deque<std::string> remaining_parts(parts.begin() + 1, parts.end()); | ||
| 368 | - findSubscriptionMatch(matching_callbacks, it->second.children, remaining_parts); | 422 | + // Recursively find the match for the next part if we didn't reach the end. |
| 423 | + if (pos != std::string_view::npos) | ||
| 424 | + { | ||
| 425 | + findSubscriptionMatch(topic, message, topic_search, it->second.children); | ||
| 426 | + } | ||
| 369 | } | 427 | } |
| 370 | 428 | ||
| 371 | // Find the match if this part is a wildcard. | 429 | // Find the match if this part is a wildcard. |
| @@ -374,10 +432,16 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector<std::function<voi | @@ -374,10 +432,16 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector<std::function<voi | ||
| 374 | { | 432 | { |
| 375 | LOG_TRACE(this, "Found subscription match for '+' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); | 433 | LOG_TRACE(this, "Found subscription match for '+' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); |
| 376 | 434 | ||
| 377 | - matching_callbacks.insert(matching_callbacks.end(), it->second.callbacks.begin(), it->second.callbacks.end()); | 435 | + for (const auto &callback : it->second.callbacks) |
| 436 | + { | ||
| 437 | + callback(topic, message); | ||
| 438 | + } | ||
| 378 | 439 | ||
| 379 | - std::deque<std::string> remaining_parts(parts.begin() + 1, parts.end()); | ||
| 380 | - findSubscriptionMatch(matching_callbacks, it->second.children, remaining_parts); | 440 | + // Recursively find the match for the next part if we didn't reach the end. |
| 441 | + if (pos != std::string_view::npos) | ||
| 442 | + { | ||
| 443 | + findSubscriptionMatch(topic, message, topic_search, it->second.children); | ||
| 444 | + } | ||
| 381 | } | 445 | } |
| 382 | 446 | ||
| 383 | // Find the match if the remaining is a wildcard. | 447 | // Find the match if the remaining is a wildcard. |
| @@ -386,40 +450,27 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector<std::function<voi | @@ -386,40 +450,27 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector<std::function<voi | ||
| 386 | { | 450 | { |
| 387 | LOG_TRACE(this, "Found subscription match for '#' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); | 451 | LOG_TRACE(this, "Found subscription match for '#' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); |
| 388 | 452 | ||
| 389 | - matching_callbacks.insert(matching_callbacks.end(), it->second.callbacks.begin(), it->second.callbacks.end()); | 453 | + for (const auto &callback : it->second.callbacks) |
| 454 | + { | ||
| 455 | + callback(topic, message); | ||
| 456 | + } | ||
| 457 | + | ||
| 390 | // No more recursion here, as we implicit consume the rest of the parts too. | 458 | // No more recursion here, as we implicit consume the rest of the parts too. |
| 391 | } | 459 | } |
| 392 | } | 460 | } |
| 393 | 461 | ||
| 394 | -void TrueMQTT::Client::Impl::messageReceived(std::string topic, std::string message) | 462 | +void TrueMQTT::Client::Impl::messageReceived(std::string_view topic, std::string_view message) |
| 395 | { | 463 | { |
| 396 | - LOG_TRACE(this, "Message received on topic '" + topic + "': " + message); | ||
| 397 | - | ||
| 398 | - // Split the topic on the / in parts. | ||
| 399 | - std::string part; | ||
| 400 | - std::stringstream stopic(topic); | ||
| 401 | - std::deque<std::string> parts; | ||
| 402 | - while (std::getline(stopic, part, '/')) | ||
| 403 | - { | ||
| 404 | - parts.emplace_back(part); | ||
| 405 | - } | ||
| 406 | - | ||
| 407 | - // Find the matching subscription(s) with recursion. | ||
| 408 | - std::vector<std::function<void(std::string, std::string)>> matching_callbacks; | ||
| 409 | - findSubscriptionMatch(matching_callbacks, m_subscriptions, parts); | 464 | + std::scoped_lock lock(m_state_mutex); |
| 410 | 465 | ||
| 411 | - LOG_TRACE(this, "Found " + std::to_string(matching_callbacks.size()) + " subscription(s) for topic '" + topic + "'"); | 466 | + LOG_TRACE(this, "Message received on topic '" + std::string(topic) + "': " + std::string(message)); |
| 412 | 467 | ||
| 413 | - if (matching_callbacks.size() == 1) | 468 | + if (m_state != State::CONNECTED) |
| 414 | { | 469 | { |
| 415 | - // For a single callback there is no need to copy the topic/message. | ||
| 416 | - matching_callbacks[0](std::move(topic), std::move(message)); | ||
| 417 | - } | ||
| 418 | - else | ||
| 419 | - { | ||
| 420 | - for (const auto &callback : matching_callbacks) | ||
| 421 | - { | ||
| 422 | - callback(topic, message); | ||
| 423 | - } | 470 | + // This happens easily when the subscribed to a really busy topic and you disconnect. |
| 471 | + LOG_ERROR(this, "Received message while not connected"); | ||
| 472 | + return; | ||
| 424 | } | 473 | } |
| 474 | + | ||
| 475 | + findSubscriptionMatch(topic, message, topic, m_subscriptions); | ||
| 425 | } | 476 | } |
src/ClientImpl.h
| @@ -22,9 +22,9 @@ | @@ -22,9 +22,9 @@ | ||
| 22 | class TrueMQTT::Client::Impl | 22 | class TrueMQTT::Client::Impl |
| 23 | { | 23 | { |
| 24 | public: | 24 | public: |
| 25 | - Impl(const std::string &host, | 25 | + Impl(const std::string_view host, |
| 26 | int port, | 26 | int port, |
| 27 | - const std::string &client_id, | 27 | + const std::string_view client_id, |
| 28 | std::chrono::milliseconds connection_timeout, | 28 | std::chrono::milliseconds connection_timeout, |
| 29 | std::chrono::milliseconds connection_backoff, | 29 | std::chrono::milliseconds connection_backoff, |
| 30 | std::chrono::milliseconds connection_backoff_max, | 30 | std::chrono::milliseconds connection_backoff_max, |
| @@ -41,20 +41,20 @@ public: | @@ -41,20 +41,20 @@ public: | ||
| 41 | class SubscriptionPart | 41 | class SubscriptionPart |
| 42 | { | 42 | { |
| 43 | public: | 43 | public: |
| 44 | - std::map<std::string, SubscriptionPart> children; | ||
| 45 | - std::vector<std::function<void(std::string, std::string)>> callbacks; | 44 | + std::map<std::string, SubscriptionPart, std::less<>> children; |
| 45 | + std::vector<std::function<void(std::string_view, std::string_view)>> callbacks; | ||
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | - void connect(); ///< Connect to the broker. | ||
| 49 | - void disconnect(); ///< Disconnect from the broker. | ||
| 50 | - bool sendPublish(const std::string &topic, const std::string &message, bool retain); ///< Send a publish message to the broker. | ||
| 51 | - bool sendSubscribe(const std::string &topic); ///< Send a subscribe message to the broker. | ||
| 52 | - bool sendUnsubscribe(const std::string &topic); ///< Send an unsubscribe message to the broker. | ||
| 53 | - void connectionStateChange(bool connected); ///< Called when a connection goes from CONNECTING state to CONNECTED state or visa versa. | ||
| 54 | - bool toPublishQueue(const std::string &topic, const std::string &message, bool retain); ///< Add a publish message to the publish queue. | ||
| 55 | - void messageReceived(std::string topic, std::string message); ///< Called when a message is received from the broker. | 48 | + void connect(); ///< Connect to the broker. |
| 49 | + void disconnect(); ///< Disconnect from the broker. | ||
| 50 | + bool sendPublish(const std::string_view topic, const std::string_view message, bool retain); ///< Send a publish message to the broker. | ||
| 51 | + bool sendSubscribe(const std::string_view topic); ///< Send a subscribe message to the broker. | ||
| 52 | + bool sendUnsubscribe(const std::string_view topic); ///< Send an unsubscribe message to the broker. | ||
| 53 | + void connectionStateChange(bool connected); ///< Called when a connection goes from CONNECTING state to CONNECTED state or visa versa. | ||
| 54 | + bool toPublishQueue(const std::string_view topic, const std::string_view message, bool retain); ///< Add a publish message to the publish queue. | ||
| 55 | + void messageReceived(std::string_view topic, std::string_view message); ///< Called when a message is received from the broker. | ||
| 56 | 56 | ||
| 57 | - void findSubscriptionMatch(std::vector<std::function<void(std::string, std::string)>> &callbacks, const std::map<std::string, SubscriptionPart> &subscriptions, std::deque<std::string> &parts); ///< Recursive function to find any matching subscription based on parts. | 57 | + void findSubscriptionMatch(std::string_view topic, std::string_view message, std::string_view topic_search, const std::map<std::string, Client::Impl::SubscriptionPart, std::less<>> &subscriptions); ///< Recursive function to find any matching subscription based on parts. |
| 58 | 58 | ||
| 59 | State m_state = State::DISCONNECTED; ///< The current state of the client. | 59 | State m_state = State::DISCONNECTED; ///< The current state of the client. |
| 60 | std::mutex m_state_mutex; ///< Mutex to protect state changes. | 60 | std::mutex m_state_mutex; ///< Mutex to protect state changes. |
| @@ -67,14 +67,14 @@ public: | @@ -67,14 +67,14 @@ public: | ||
| 67 | std::chrono::milliseconds m_connection_backoff_max; ///< Maximum time between backoff attempts in seconds. | 67 | std::chrono::milliseconds m_connection_backoff_max; ///< Maximum time between backoff attempts in seconds. |
| 68 | std::chrono::milliseconds m_keep_alive_interval; ///< Interval in seconds between keep-alive messages. | 68 | std::chrono::milliseconds m_keep_alive_interval; ///< Interval in seconds between keep-alive messages. |
| 69 | 69 | ||
| 70 | - Client::LogLevel m_log_level = Client::LogLevel::NONE; ///< The log level to use. | ||
| 71 | - std::function<void(Client::LogLevel, std::string)> m_logger = [](Client::LogLevel, std::string) { /* empty */ }; ///< Logger callback. | 70 | + Client::LogLevel m_log_level = Client::LogLevel::NONE; ///< The log level to use. |
| 71 | + std::function<void(Client::LogLevel, std::string_view)> m_logger = [](Client::LogLevel, std::string_view) { /* empty */ }; ///< Logger callback. | ||
| 72 | 72 | ||
| 73 | std::string m_last_will_topic = ""; ///< Topic to publish the last will message to. | 73 | std::string m_last_will_topic = ""; ///< Topic to publish the last will message to. |
| 74 | std::string m_last_will_message = ""; ///< Message to publish on the last will topic. | 74 | std::string m_last_will_message = ""; ///< Message to publish on the last will topic. |
| 75 | bool m_last_will_retain = false; ///< Whether to retain the last will message. | 75 | bool m_last_will_retain = false; ///< Whether to retain the last will message. |
| 76 | 76 | ||
| 77 | - std::function<void(Error, std::string)> m_error_callback = [](Error, std::string) { /* empty */ }; ///< Error callback. | 77 | + std::function<void(Error, std::string_view)> m_error_callback = [](Error, std::string_view) { /* empty */ }; ///< Error callback. |
| 78 | 78 | ||
| 79 | Client::PublishQueueType m_publish_queue_type = Client::PublishQueueType::DROP; ///< The type of queue to use for the publish queue. | 79 | Client::PublishQueueType m_publish_queue_type = Client::PublishQueueType::DROP; ///< The type of queue to use for the publish queue. |
| 80 | size_t m_publish_queue_size = -1; ///< Size of the publish queue. | 80 | size_t m_publish_queue_size = -1; ///< Size of the publish queue. |
| @@ -82,8 +82,8 @@ public: | @@ -82,8 +82,8 @@ public: | ||
| 82 | 82 | ||
| 83 | size_t m_send_queue_size = 1000; ///< Size of the send queue. | 83 | size_t m_send_queue_size = 1000; ///< Size of the send queue. |
| 84 | 84 | ||
| 85 | - std::set<std::string> m_subscription_topics; ///< Flat list of topics the client is subscribed to. | ||
| 86 | - std::map<std::string, SubscriptionPart> m_subscriptions; ///< Tree of active subscriptions build up from the parts on the topic. | 85 | + std::set<std::string, std::less<>> m_subscription_topics; ///< Flat list of topics the client is subscribed to. |
| 86 | + std::map<std::string, SubscriptionPart, std::less<>> m_subscriptions; ///< Tree of active subscriptions build up from the parts on the topic. | ||
| 87 | 87 | ||
| 88 | class Connection; | 88 | class Connection; |
| 89 | std::unique_ptr<Connection> m_connection; ///< Connection to the broker. | 89 | std::unique_ptr<Connection> m_connection; ///< Connection to the broker. |
src/Packet.cpp
| @@ -177,19 +177,19 @@ bool TrueMQTT::Client::Impl::Connection::recvLoop() | @@ -177,19 +177,19 @@ bool TrueMQTT::Client::Impl::Connection::recvLoop() | ||
| 177 | } | 177 | } |
| 178 | case Packet::PacketType::PUBLISH: | 178 | case Packet::PacketType::PUBLISH: |
| 179 | { | 179 | { |
| 180 | - std::string topic; | 180 | + std::string_view topic; |
| 181 | if (!packet.read_string(topic)) | 181 | if (!packet.read_string(topic)) |
| 182 | { | 182 | { |
| 183 | LOG_ERROR(&m_impl, "Malformed packet received, closing connection"); | 183 | LOG_ERROR(&m_impl, "Malformed packet received, closing connection"); |
| 184 | return false; | 184 | return false; |
| 185 | } | 185 | } |
| 186 | 186 | ||
| 187 | - std::string message; | 187 | + std::string_view message; |
| 188 | packet.read_remaining(message); | 188 | packet.read_remaining(message); |
| 189 | 189 | ||
| 190 | - LOG_DEBUG(&m_impl, "Received PUBLISH with topic " + topic + ": " + message); | 190 | + LOG_DEBUG(&m_impl, "Received PUBLISH with topic " + std::string(topic) + ": " + std::string(message)); |
| 191 | 191 | ||
| 192 | - m_impl.messageReceived(std::move(topic), std::move(message)); | 192 | + m_impl.messageReceived(topic, message); |
| 193 | break; | 193 | break; |
| 194 | } | 194 | } |
| 195 | case Packet::PacketType::SUBACK: | 195 | case Packet::PacketType::SUBACK: |
| @@ -288,7 +288,6 @@ bool TrueMQTT::Client::Impl::Connection::send(Packet packet, bool has_priority) | @@ -288,7 +288,6 @@ bool TrueMQTT::Client::Impl::Connection::send(Packet packet, bool has_priority) | ||
| 288 | { | 288 | { |
| 289 | m_send_queue.push_back(std::move(packet)); | 289 | m_send_queue.push_back(std::move(packet)); |
| 290 | } | 290 | } |
| 291 | - | ||
| 292 | } | 291 | } |
| 293 | // Notify the write thread that there is a new packet. | 292 | // Notify the write thread that there is a new packet. |
| 294 | m_send_queue_cv.notify_one(); | 293 | m_send_queue_cv.notify_one(); |
| @@ -374,9 +373,9 @@ bool TrueMQTT::Client::Impl::Connection::sendPingRequest() | @@ -374,9 +373,9 @@ bool TrueMQTT::Client::Impl::Connection::sendPingRequest() | ||
| 374 | return send(std::move(packet), true); | 373 | return send(std::move(packet), true); |
| 375 | } | 374 | } |
| 376 | 375 | ||
| 377 | -bool TrueMQTT::Client::Impl::sendPublish(const std::string &topic, const std::string &message, bool retain) | 376 | +bool TrueMQTT::Client::Impl::sendPublish(const std::string_view topic, const std::string_view message, bool retain) |
| 378 | { | 377 | { |
| 379 | - LOG_TRACE(this, "Sending PUBLISH packet to topic '" + topic + "': " + message + " (" + (retain ? "retained" : "not retained") + ")"); | 378 | + LOG_TRACE(this, "Sending PUBLISH packet to topic '" + std::string(topic) + "': " + std::string(message) + " (" + (retain ? "retained" : "not retained") + ")"); |
| 380 | 379 | ||
| 381 | uint8_t flags = 0; | 380 | uint8_t flags = 0; |
| 382 | flags |= (retain ? 1 : 0) << 0; // Retain | 381 | flags |= (retain ? 1 : 0) << 0; // Retain |
| @@ -386,14 +385,14 @@ bool TrueMQTT::Client::Impl::sendPublish(const std::string &topic, const std::st | @@ -386,14 +385,14 @@ bool TrueMQTT::Client::Impl::sendPublish(const std::string &topic, const std::st | ||
| 386 | Packet packet(Packet::PacketType::PUBLISH, flags); | 385 | Packet packet(Packet::PacketType::PUBLISH, flags); |
| 387 | 386 | ||
| 388 | packet.write_string(topic); | 387 | packet.write_string(topic); |
| 389 | - packet.write(message.c_str(), message.size()); | 388 | + packet.write(message.data(), message.size()); |
| 390 | 389 | ||
| 391 | return m_connection->send(std::move(packet)); | 390 | return m_connection->send(std::move(packet)); |
| 392 | } | 391 | } |
| 393 | 392 | ||
| 394 | -bool TrueMQTT::Client::Impl::sendSubscribe(const std::string &topic) | 393 | +bool TrueMQTT::Client::Impl::sendSubscribe(const std::string_view topic) |
| 395 | { | 394 | { |
| 396 | - LOG_TRACE(this, "Sending SUBSCRIBE packet for topic '" + topic + "'"); | 395 | + LOG_TRACE(this, "Sending SUBSCRIBE packet for topic '" + std::string(topic) + "'"); |
| 397 | 396 | ||
| 398 | Packet packet(Packet::PacketType::SUBSCRIBE, 2); | 397 | Packet packet(Packet::PacketType::SUBSCRIBE, 2); |
| 399 | 398 | ||
| @@ -410,9 +409,9 @@ bool TrueMQTT::Client::Impl::sendSubscribe(const std::string &topic) | @@ -410,9 +409,9 @@ bool TrueMQTT::Client::Impl::sendSubscribe(const std::string &topic) | ||
| 410 | return m_connection->send(std::move(packet)); | 409 | return m_connection->send(std::move(packet)); |
| 411 | } | 410 | } |
| 412 | 411 | ||
| 413 | -bool TrueMQTT::Client::Impl::sendUnsubscribe(const std::string &topic) | 412 | +bool TrueMQTT::Client::Impl::sendUnsubscribe(const std::string_view topic) |
| 414 | { | 413 | { |
| 415 | - LOG_TRACE(this, "Sending unsubscribe message for topic '" + topic + "'"); | 414 | + LOG_TRACE(this, "Sending unsubscribe message for topic '" + std::string(topic) + "'"); |
| 416 | 415 | ||
| 417 | Packet packet(Packet::PacketType::UNSUBSCRIBE, 2); | 416 | Packet packet(Packet::PacketType::UNSUBSCRIBE, 2); |
| 418 | 417 |
src/Packet.h
| @@ -66,10 +66,10 @@ public: | @@ -66,10 +66,10 @@ public: | ||
| 66 | m_buffer.insert(m_buffer.end(), data, data + length); | 66 | m_buffer.insert(m_buffer.end(), data, data + length); |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | - void write_string(const std::string &str) | 69 | + void write_string(const std::string_view str) |
| 70 | { | 70 | { |
| 71 | write_uint16(static_cast<uint16_t>(str.size())); | 71 | write_uint16(static_cast<uint16_t>(str.size())); |
| 72 | - write(str.c_str(), str.size()); | 72 | + write(str.data(), str.size()); |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | bool read_uint8(uint8_t &value) | 75 | bool read_uint8(uint8_t &value) |
| @@ -93,7 +93,7 @@ public: | @@ -93,7 +93,7 @@ public: | ||
| 93 | return true; | 93 | return true; |
| 94 | } | 94 | } |
| 95 | 95 | ||
| 96 | - bool read_string(std::string &str) | 96 | + bool read_string(std::string_view &str) |
| 97 | { | 97 | { |
| 98 | uint16_t length; | 98 | uint16_t length; |
| 99 | if (!read_uint16(length)) | 99 | if (!read_uint16(length)) |
| @@ -104,16 +104,15 @@ public: | @@ -104,16 +104,15 @@ public: | ||
| 104 | { | 104 | { |
| 105 | return false; | 105 | return false; |
| 106 | } | 106 | } |
| 107 | - const char *data = reinterpret_cast<const char *>(m_buffer.data()) + m_read_offset; | ||
| 108 | - str.assign(data, length); | 107 | + |
| 108 | + str = std::string_view(reinterpret_cast<const char *>(m_buffer.data() + m_read_offset), length); | ||
| 109 | m_read_offset += length; | 109 | m_read_offset += length; |
| 110 | return true; | 110 | return true; |
| 111 | } | 111 | } |
| 112 | 112 | ||
| 113 | - void read_remaining(std::string &str) | 113 | + void read_remaining(std::string_view &str) |
| 114 | { | 114 | { |
| 115 | - const char *data = reinterpret_cast<const char *>(m_buffer.data()) + m_read_offset; | ||
| 116 | - str.assign(data, m_buffer.size() - m_read_offset); | 115 | + str = std::string_view(reinterpret_cast<const char *>(m_buffer.data() + m_read_offset), m_buffer.size() - m_read_offset); |
| 117 | m_read_offset = m_buffer.size(); | 116 | m_read_offset = m_buffer.size(); |
| 118 | } | 117 | } |
| 119 | 118 |