1
0
mirror of https://github.com/eclipse/paho.mqtt.cpp.git synced 2025-05-09 03:11:23 +08:00
paho.mqtt.cpp/examples/multithr_pub_sub.cpp
2024-06-16 14:07:10 -04:00

214 lines
6.3 KiB
C++

// async_subscribe.cpp
//
// This is a Paho MQTT C++ client, sample application.
//
// This application is an MQTT publisher/subscriber using the C++
// asynchronous client interface, demonstrating how you can share a client
// between multiple threads.
//
// The app will count the number of "data" messages arriving at the broker
// and then emit "events" with updated counts. A data message is any on a
// "data/#" topic, and counts are emitted on the "events/count" topic. It
// emits an event count around once every ten data messages.
//
// Note that this is a fairly contrived example, and it could be done much
// more easily in a single thread. It is meant to demonstrate how you can
// share a client amongst threads if and when that's a proper thing to do.
//
// At this time, there is a single callback or consumer queue for all
// incoming messages, so you would typically only have one thead receiving
// messages, although it _could_ send messages to multiple threads for
// processing, perhaps based on the topics. It could be common, however, to
// want to have multiple threads for publishing.
//
// The sample demonstrates:
// - Creating a client and accessing it from a shared_ptr<>
// - Using one thread to receive incoming messages from the broker and
// another thread to publish messages to it.
// - Connecting to an MQTT server/broker.
// - Subscribing to a topic
// - Using the asynchronous consumer
// - Publishing messages.
//
/*******************************************************************************
* Copyright (c) 2020-2023 Frank Pagliughi <fpagliughi@mindspring.com>
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Frank Pagliughi - initial implementation and documentation
*******************************************************************************/
#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cctype>
#include <thread>
#include <chrono>
#include <memory>
#include "mqtt/async_client.h"
using namespace std;
using namespace std::chrono;
const std::string DFLT_SERVER_ADDRESS("mqtt://localhost:1883");
const std::string CLIENT_ID("multithr_pub_sub_cpp");
/////////////////////////////////////////////////////////////////////////////
/**
* A thread-safe counter that can be used to occasionally signal a waiter on
* every 10th increment.
*/
class multithr_counter
{
using guard = std::unique_lock<std::mutex>;
size_t count_;
bool closed_;
mutable bool ready_;
mutable std::condition_variable cond_;
mutable std::mutex lock_;
public:
// Declare a pointer type for sharing a counter between threads
using ptr_t = std::shared_ptr<multithr_counter>;
// Create a new thread-safe counter with an initial count of zero.
multithr_counter() : count_(0), closed_(false), ready_(false) {}
// Determines if the counter has been closed.
bool closed() const {
guard g(lock_);
return closed_;
}
// Close the counter and signal all waiters.
void close() {
guard g(lock_);
closed_ = ready_ = true;
cond_.notify_all();
}
// Increments the count, and then signals once every 10 messages.
void incr() {
guard g(lock_);
if (closed_)
throw string("Counter is closed");
if (++count_ % 10 == 0) {
ready_ = true;
g.unlock();
cond_.notify_all();
}
}
// This will block the caller until at least 10 new messages received.
size_t get_count() const {
guard g(lock_);
cond_.wait(g, [this]{ return ready_; });
ready_ = false;
return count_;
}
};
/////////////////////////////////////////////////////////////////////////////
// The MQTT publisher function will run in its own thread.
// It runs until the receiver thread closes the counter object.
void publisher_func(mqtt::async_client_ptr cli, multithr_counter::ptr_t counter)
{
while (true) {
size_t n = counter->get_count();
if (counter->closed()) break;
string payload = std::to_string(n);
cli->publish("events/count", payload)->wait();
}
}
/////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
string address = (argc > 1) ? string(argv[1]) : DFLT_SERVER_ADDRESS;
// Create an MQTT client using a smart pointer to be shared among threads.
auto cli = std::make_shared<mqtt::async_client>(address, CLIENT_ID);
// Make a counter object also with a shared pointer.
auto counter = std::make_shared <multithr_counter>();
// Connect options for a persistent session and automatic reconnects.
auto connOpts = mqtt::connect_options_builder()
.clean_session(false)
.automatic_reconnect(seconds(2), seconds(30))
.finalize();
auto TOPICS = mqtt::string_collection::create({ "data/#", "command" });
const vector<int> QOS { 0, 1 };
try {
// Start consuming _before_ connecting, because we could get a flood
// of stored messages as soon as the connection completes since
// we're using a persistent (non-clean) session with the broker.
cli->start_consuming();
cout << "Connecting to the MQTT server at " << address << "..." << flush;
auto rsp = cli->connect(connOpts)->get_connect_response();
cout << "OK\n" << endl;
// Subscribe if this is a new session with the server
if (!rsp.is_session_present())
cli->subscribe(TOPICS, QOS);
// Start the publisher thread
std::thread publisher(publisher_func, cli, counter);
// Consume messages in this thread
while (true) {
auto msg = cli->consume_message();
if (!msg)
continue;
if (msg->get_topic() == "command" &&
msg->to_string() == "exit") {
cout << "Exit command received" << endl;
break;
}
cout << msg->get_topic() << ": " << msg->to_string() << endl;
counter->incr();
}
// Close the counter and wait for the publisher thread to complete
cout << "\nShutting down..." << flush;
counter->close();
publisher.join();
// Disconnect
cout << "OK\nDisconnecting..." << flush;
cli->disconnect();
cout << "OK" << endl;
}
catch (const mqtt::exception& exc) {
cerr << exc.what() << endl;
return 1;
}
return 0;
}