This tutorial demonstrates how Mongoose Library can be used to implement an MQTT client. We'll create an MQTT client that:
- Connects to the public HiveMQ MQTT server
- When connected, subscribes to the topic
- Publishes a
hellomessage to the
- Receives that message back from the subscribed topic and closes the connection
- Creates a timer that checks if a connection is closed, and reconnects to repeat the whole process
The full source code for this tutorial is at https://github.com/cesanta/mongoose/tree/master/examples/mqtt-client
The MQTT API is rich, here we'll only use three functions:
struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, struct mg_mqtt_opts *opts, mg_event_handler_t fn, void *fn_data); void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic, struct mg_str data, int qos, bool retain); void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos);
mg_mqtt_connect() tries to connect to the MQTT broker whose URL is specified in parameter
url, returning a connection handler or NULL on failure. The function specified in parameter
fn will be our callback function, and parameter
fn_data will be its argument. We can indicate session and last will options in a structure pointed to by the
mg_mqtt_pub() performs the action of publishing the message
data to the topic
mg_mqtt_sub() performs the action of subscribing to the topic
For detailed information, see the documentation.
First we declare global variables to make it easy to change tunable parameters, like MQTT server address, topic names, and QoS level.
Also, we make an MQTT connection pointer,
s_conn, globally visible.
A reconnection timer function is going to check that pointer for NULL,
and if it is NULL, a timer function will create a new connection. Then,
a connection event handler sets this pointer to NULL when a connection closes.
main() function is simple: we initialise an event manager and
start the event loop, as usual. Also we create a timer - note that
a timer is created on the stack but since it is declared in
main(), it lives
as long as the application is running.
Note that the timer is initialised after the event manager.
This is important because here the timer
is created with the
MG_TIMER_RUN_NOW flag, which executes the timer function
immediately. Our timer function will use the event manager, so it must be initialised beforehand.
The timer function is called with an interval of 3 seconds; and our event manager poll timeout is 1 second.
For more information on timers, check the timers tutorial.
The event loop executes until a termination signal is received. After that, the
mg_timer_free() functions are called to perform
The timer function implements the reconnection logic and is trivial. It creates
a client connection
s_conn if it is NULL. This pattern should be used for
any type of client connection that must be kept alive.
The event handler function checks which event
ev has arrived and acts
MG_EV_OPEN event is the very first event that is sent
to every connection when it is just created and added to the event manager.
In our case, we simply log that fact:
Connection failures will trigger an
MG_EV_ERROR event, an error reason is passed as a
char * pointer in parameter
check the error handling tutorial for more information.
Here, on any error - for example, DNS lookup failure, we log that error:
When the TCP connection to the MQTT server is established, we initialise TLS
if the MQTT server URL is
Note that, for that to work, the application must be built with TLS enabled. By default, the HiveMQ URL is not TLS, so building with TLS is not required; more information on how to build can be found below.
Then we catch the
MG_EV_MQTT_OPEN event which is sent when the MQTT server accepted
us as a client. There, we subscribe to the desired topic, and immediately publish
to it. Note that these functions do not immediately send data to the
MQTT server. As any other Mongoose output function, they just append data
to the output buffer. Data gets sent when we exit the event handler and
Mongoose performs the event manager poll,
When we receive a message - and we should, we print it and signal Mongoose to terminate the connection:
And when we are getting closed, we set
s_conn to NULL. The timer function
will kick in the reconnection later:
Build and run
- If you've not already done so, clone the Mongoose Library repo
$ git clone https://github.com/cesanta/mongoose
- Build the example, this will also start the MQTT client:
$ cd mongoose/examples/mqtt-client $ make clean all
- Observe the log
1814f03947d 2 main.c:30:fn CREATED 1814f03968b 2 main.c:45:fn CONNECTED to mqtt://broker.hivemq.com:1883 1814f03968b 2 main.c:47:fn SUBSCRIBED to mg/+/test 1814f03968b 2 main.c:51:fn PUBLISHED hello -> mg/clnt/test 1814f03978a 2 main.c:56:fn RECEIVED hello <- mg/clnt/test 1814f03978a 2 main.c:59:fn CLOSED 1814f03a344 2 main.c:30:fn CREATED 1814f03a58b 2 main.c:45:fn CONNECTED to mqtt://broker.hivemq.com:1883 1814f03a58b 2 main.c:47:fn SUBSCRIBED to mg/+/test 1814f03a58b 2 main.c:51:fn PUBLISHED hello -> mg/clnt/test 1814f03a681 2 main.c:56:fn RECEIVED hello <- mg/clnt/test 1814f03a681 2 main.c:59:fn CLOSED
Build with TLS support
The makefile will take care of this, just pass it the proper argument.
$ make clean all SSL=OPENSSL
$ make clean all SSL=MBEDTLS
If you need to provide extra information, like for example includes and library paths, do it with the EXTRA_CFLAGS variable, like:
$ make clean all SSL=MBEDTLS EXTRA_CFLAGS="-I/path/to/mbedtls/include -L/path/to/mbedtls/lib"
For more information on building TLS clients, check the TLS tutorial