In a previous project, I used MQTTnet for MQTT communication. Initially, I used the standard MqttClient, which required me to write my own reconnection logic—code that was messy and overly long. Later, I discovered that MQTTnet offers an extension package called ManagedMqttClient, which handles reconnections and offline message queuing automatically, making it much more pleasant to use.

Additionally, I’ve always struggled to remember the differences between QoS, CleanSession, and Retain in MQTT, so I’ll note them down here for reference.


Installation

dotnet add package MQTTnet
dotnet add package MQTTnet.Extensions.ManagedClient

ManagedMqttClient is a standalone extension package, not part of the main MQTTnet library.

Basic Usage

var options = new ManagedMqttClientOptionsBuilder()
    .WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
    .WithClientOptions(new MqttClientOptionsBuilder()
        .WithClientId("my-client-001")
        .WithTcpServer("broker.hivemq.com", 1883)
        .WithCredentials("username", "password")
        .WithCleanSession(false)
        .Build())
    .Build();

var mqttClient = new MqttFactory().CreateManagedMqttClient();

// 收到消息的回调
mqttClient.ApplicationMessageReceivedAsync += e =>
{
    var topic = e.ApplicationMessage.Topic;
    var payload = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
    Console.WriteLine($"收到消息 [{topic}]: {payload}");
    return Task.CompletedTask;
};

// 连接状态变化
mqttClient.ConnectedAsync += e =>
{
    Console.WriteLine("已连接到 Broker");
    return Task.CompletedTask;
};

mqttClient.DisconnectedAsync += e =>
{
    Console.WriteLine("连接断开,等待重连...");
    return Task.CompletedTask;
};

// 订阅主题
await mqttClient.SubscribeAsync(
    new MqttTopicFilterBuilder()
        .WithTopic("device/sensor/temperature")
        .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
        .Build());

// 启动
await mqttClient.StartAsync(options);

StartAsync returns immediately after being called, and it internally starts a thread to maintain the connection. After a disconnection, it will automatically reconnect at intervals specified by WithAutoReconnectDelay. Once reconnected, previous subscriptions will also be restored automatically, so you don't need to manually call Subscribe again.

Publishing Messages

await mqttClient.EnqueueAsync(
    new MqttApplicationMessageBuilder()
        .WithTopic("device/sensor/temperature")
        .WithPayload("26.5")
        .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
        .WithRetainFlag(false)
        .Build());

Note that EnqueueAsync is used here, not PublishAsync. The message enters an internal queue first and is sent automatically when the connection is normal. If the current state is disconnected, the message waits in the queue and is sent after a successful reconnection. This is one of the advantages of ManagedMqttClient over the standard MqttClient.

QoS (Quality of Service)

MQTT has three QoS levels that control delivery guarantees:

QoS 0 — At Most Once

The message is sent without confirmation or retry. It is the fastest but may lose messages. It is suitable for scenarios where losing a message is acceptable, such as a sensor reporting temperature once per second; losing one or two readings has no significant impact.

QoS 1 — At Least Once

After the Broker receives the message, it sends back an acknowledgment (PUBACK). If the sender does not receive this acknowledgment, it retries sending. This ensures messages are not lost but may result in duplicates. Using this level is sufficient for most scenarios.

QoS 2 — Exactly Once

Through a four-step handshake (PUBLISH → PUBREC → PUBREL → PUBCOMP), it guarantees that messages are neither lost nor duplicated. It is the most reliable but also the slowest and has the highest overhead. It should only be used in scenarios where duplicates are unacceptable, such as payment notifications or command issuance.

In practical projects, QoS 1 is used in most cases. QoS 2 should be avoided if possible, as the performance difference is quite noticeable.

CleanSession (Clear Session)

When connecting to the Broker, there is a CleanSession flag:

CleanSession = true

Each connection is completely new, and the Broker does not retain any state related to you. Any messages sent to you while disconnected are discarded. After reconnecting, you need to resubscribe to topics.

CleanSession = false

The Broker remembers your subscriptions and the QoS 1/2 messages received during your offline period. When you reconnect, the Broker pushes these buffered messages to you. The prerequisite is that your ClientId must be fixed and not randomly generated for each connection.

If your scenario involves devices that occasionally lose network connectivity and need to receive missed messages after reconnection, set CleanSession to false. If you do not care about offline messages, setting it to true is fine, which also reduces the load on the Broker.

Retain (Retained Message)

When publishing a message, you can set a Retain flag:

Retain = true

The Broker retains the last message with the Retain flag on this Topic. When a new subscriber subscribes to this Topic, they immediately receive this retained message without waiting for the next publication.

Retain = false

Once the message is sent, it is gone. New subscribers must wait for the next publication to receive the message after subscribing.

A typical scenario: After a device comes online, it publishes an online status message with Retain set. This ensures that any new client subscribing to this device's status Topic at any time can immediately know its online status without waiting for the device to report again.

To clear the retained message for a specific Topic, publish a message with an empty payload and Retain set to true.

How These Work Together

For example: A temperature sensor publishes data every 10 seconds, and a monitoring panel subscribes to this data.

Sensor side publishing: QoS 1 + Retain = true. QoS 1 ensures the message is not lost, and Retain ensures that the monitoring panel sees the latest temperature value immediately after refreshing the page, without waiting for the next 10-second interval.

Monitoring panel subscribing: QoS 1 + CleanSession = true. The panel does not need historical messages; seeing the current value each time it opens is sufficient. The retained message solves the "see it immediately upon opening" problem.

If this were an alarm system, the approach would differ: QoS 1 or 2 + CleanSession = false. No alarm messages during disconnection can be lost; all of them must be delivered after reconnection.

This content is automatically translated to English. View Original