Overview

This tutorial demonstrates how Mongoose Library can be used to implement an MQTT client that connects to the broker over WebSocket.

Our actions will be the following:

  • Initiate a Websocket connection to the public HiveMQ MQTT broker ws://broker.hivemq.com:8000/mqtt
  • When connected, send an MQTT login message inside a WebSocket message. From now on, WebSocket messages will carry MQTT messages
  • Subscribe to the topic mg/test
  • Publish a hello message to the mg/test topic
  • Receive that message back from the subscribed topic and close the connection

The details of the WebSocket client can be gathered from its tutorial. For the MQTT client details, there is its tutorial too, which also implements an automatic reconnection functionality.

API

From the MQTT API, we'll use:

void mg_mqtt_login(struct mg_connection *c, const char *url,
                   struct mg_mqtt_opts *opts);
int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, struct mg_mqtt_message *m);

The function mg_mqtt_login() tries to login to the MQTT broker whose URL is specified in parameter url, which will also contain any login credentials needed. We can indicate session and last will options in a structure pointed to by the opts parameter. Note that this function relies on a connection already established.

The function mg_mqtt_parse() tries to parse the buffer buf and fill the mg_mqtt_message structure m if the buffer contains a proper (and complete) MQTT message. It will signal if there is an error or we need to call it again when more data will arrive to the buffer. Note that parameter version is not present in releases 7.7 and earlier.

We'll also use mg_mqtt_pub() and mg_mqtt_sub() to publish and subscribe, as we described in the MQTT client tutorial.

For detailed information, see the documentation.

From the WebSocket API, we'll use:

size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op)

The function mg_ws_wrap() converts data in the output buffer to the WebSocket format. Parameter len contains the payload length, while parameter op specifies the type of WebSocket message.

We'll also use mg_ws_connect() to connect to the server, as we described in the WebSocket client tutorial.

For detailed information, see the documentation.

Main function

The main() function is as usual, we initialize an event manager, create the WebSocket connection by calling mg_ws_connect(), and start the event loop.

The event loop executes until a termination signal is received. After that, the mg_mgr_free() function is called to perform cleanup.

Event handler

Connection failures will trigger an MG_EV_ERROR event, an error reason is passed as a char * pointer in parameter ev_data; 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 server is established, we initialise TLS if the URL is wss://:

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.

When the WebSocket negotiation is complete and the connection has been upgraded, an MG_EV_WS_OPEN event is sent. Then the connection becomes a full-duplex connection that triggers MG_EV_WS_MSG events for every message received. Here we will parse those messages for valid MQTT messages:

First we expect an MQTT_CMD_CONNACK message, then we ask for the MG_EV_MQTT_OPEN event to be triggered, as this is sent when the MQTT server accepts us as a client.

If authorization is successful, we subscribe to the desired topic, and immediately publish to it. Note that we use the function mg_ws_wrap() to wrap each MQTT message with a proper WebSocket header for a binary message

Then we expect an MQTT_CMD_PUBLISH message, corresponding to the broker sending back the message we just sent, since we subscribed to the very same topic. We print the message and signal Mongoose to terminate the connection:

On error or connection closure, we signal there is time to exit; check the error handling tutorial for more information.

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 over WebSocket client:
    $ cd mongoose/examples/mqtt-over-ws-client
    $ make clean all
    
  • Observe the log
    273058bd 2 main.c:30:fn                 Connected to WS. Logging in to MQTT...
    273059ba 2 main.c:41:fn                 GOT 4 bytes WS msg
    273059ba 2 main.c:50:fn                 CONNECTED to ws://broker.hivemq.com:8000/mqtt
    273059ba 2 main.c:53:fn                 SUBSCRIBED to mg/test
    273059ba 2 main.c:56:fn                 PUBLISHED hello -> mg/test
    27305abb 2 main.c:41:fn                 GOT 5 bytes WS msg
    27305ac4 2 main.c:41:fn                 GOT 18 bytes WS msg
    27305ac4 2 main.c:67:fn                 RECEIVED hello <- mg/test
    

Build with TLS support

The makefile will take care of this, just pass it the proper argument.

  • For openSSL:

    $ make clean all SSL=OPENSSL
    
  • For mbedTLS:

    $ 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