Tutoriels IoT

Tutoriel MQTTnet en C# : créer des clients MQTT 5 en .NET avec TLS

Créez un publisher et un subscriber console .NET avec MQTTnet, les propriétés MQTT 5 et TLS sur le port 8883, puis ajoutez une logique de reconnexion et portez le même code vers un broker MQTT managé ConnectMQ privé.

Éditeur de code C# montrant un client MQTTnet publiant des données de capteur vers un broker MQTT cloud
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

MQTTnet est la bibliothèque MQTT la plus utilisée en .NET, et elle couvre tout, du simple test en console au service de production. Dans ce tutoriel, vous allez créer un publisher et un subscriber C# avec MQTTnet, utiliser les propriétés MQTT 5, chiffrer la connexion avec TLS et ajouter une logique de reconnexion fiable. Tous les exemples s'exécutent sur le serveur MQTT public gratuit broker.connectmq.com : vous pouvez donc suivre sans rien installer d'autre que le SDK .NET.

Prérequis

  • SDK .NET 8 ou plus récent (vérifiez avec dotnet --version)
  • N'importe quel éditeur : Visual Studio, Rider ou VS Code avec l'extension C#
  • Un terminal disposant d'un accès sortant au port 8883 (TLS) ou 1883 (TCP en clair)

Aucun compte n'est nécessaire pour la première partie. Le serveur public broker.connectmq.com accepte les clients anonymes avec n'importe quel client ID unique et non vide. N'oubliez pas que l'espace public est lisible par tous : utilisez un préfixe de topic aléatoire et n'y envoyez jamais de données réelles.

Versions de MQTTnet : à lire en premier

MQTTnet a fait l'objet d'un important nettoyage de son API en version 5. Si vous copiez des extraits d'anciens articles de blog, vous rencontrerez des erreurs de compilation. Les différences les plus importantes :

Sujet MQTTnet 4.x MQTTnet 5.x
Classe factory MqttFactory MqttClientFactory
Namespaces MQTTnet.Client, MQTTnet Essentiellement regroupés dans MQTTnet
Managed client (reconnexion auto) MQTTnet.Extensions.ManagedClient Supprimé, écrivez votre propre boucle
Type de payload byte[] / ArraySegment<byte> ReadOnlySequence<byte>
Configuration TLS WithTls() (obsolète) ou WithTlsOptions WithTlsOptions(o => o.UseTls())

Ce guide cible MQTTnet 5. Les concepts restent valables pour la 4.x, mais les noms de classes diffèrent comme indiqué ci-dessus. Le dépôt officiel MQTTnet propose des exemples pour chaque fonctionnalité si vous voulez aller plus loin.

Créer les projets

Créez une solution avec deux applications console :

mkdir MqttNetDemo && cd MqttNetDemo
dotnet new console -n Publisher
dotnet new console -n Subscriber
dotnet add Publisher package MQTTnet
dotnet add Subscriber package MQTTnet

Choisissez dès maintenant un préfixe de topic unique et utilisez-le dans les deux applications. Quelque chose comme connectmq/demo/k7f3q9/ convient. Les collisions sur un broker partagé sont une source classique de confusion du type « pourquoi est-ce que je reçois des messages que je n'ai jamais envoyés ? ».

Votre premier subscriber MQTTnet

Remplacez Subscriber/Program.cs par le code ci-dessous. Il se connecte en TLS sur le port 8883, utilise MQTT 5, s'abonne en QoS 1 et affiche chaque message avec ses propriétés MQTT 5.

using System.Text;
using MQTTnet;
using MQTTnet.Formatter;
using MQTTnet.Protocol;

const string Prefix = "connectmq/demo/k7f3q9";

var factory = new MqttClientFactory();
using var client = factory.CreateMqttClient();

var options = new MqttClientOptionsBuilder()
    .WithTcpServer("broker.connectmq.com", 8883)
    .WithClientId($"sub-{Guid.NewGuid():N}")
    .WithProtocolVersion(MqttProtocolVersion.V500)
    .WithTlsOptions(o => o.UseTls())
    .WithCleanStart(true)
    .WithKeepAlivePeriod(TimeSpan.FromSeconds(30))
    .Build();

client.ApplicationMessageReceivedAsync += e =>
{
    var msg = e.ApplicationMessage;
    var payload = msg.ConvertPayloadToString();
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg.Topic} (QoS {(int)msg.QualityOfServiceLevel}, retain={msg.Retain})");
    Console.WriteLine($"  payload: {payload}");
    if (!string.IsNullOrEmpty(msg.ContentType))
        Console.WriteLine($"  content-type: {msg.ContentType}");
    foreach (var p in msg.UserProperties ?? [])
        Console.WriteLine($"  user-property: {p.Name}={p.Value}");
    return Task.CompletedTask;
};

var result = await client.ConnectAsync(options, CancellationToken.None);
Console.WriteLine($"Connected: {result.ResultCode}");

var subscribe = new MqttClientSubscribeOptionsBuilder()
    .WithTopicFilter(f => f
        .WithTopic($"{Prefix}/#")
        .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce))
    .Build();

var subResult = await client.SubscribeAsync(subscribe, CancellationToken.None);
foreach (var item in subResult.Items)
    Console.WriteLine($"Subscribed {item.TopicFilter.Topic}: {item.ResultCode}");

Console.WriteLine("Waiting for messages. Press Enter to exit.");
Console.ReadLine();

await client.DisconnectAsync(new MqttClientDisconnectOptionsBuilder()
    .WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection)
    .Build());

Lancez-le avec dotnet run --project Subscriber. Vous devriez voir Connected: Success ainsi qu'une QoS accordée pour l'abonnement. Notez que le handler est enregistré avant ConnectAsync : avec une session persistante, des messages peuvent arriver dès l'ouverture de la connexion, et un handler enregistré trop tard les manquerait.

Publier avec MQTTnet et les propriétés MQTT 5

Passons à Publisher/Program.cs. Il envoie un relevé JSON toutes les deux secondes avec un content type, une user property et un intervalle d'expiration du message, ainsi qu'un message de statut retained.

using System.Text.Json;
using MQTTnet;
using MQTTnet.Formatter;
using MQTTnet.Protocol;

const string Prefix = "connectmq/demo/k7f3q9";

var factory = new MqttClientFactory();
using var client = factory.CreateMqttClient();

var options = new MqttClientOptionsBuilder()
    .WithTcpServer("broker.connectmq.com", 8883)
    .WithClientId($"pub-{Guid.NewGuid():N}")
    .WithProtocolVersion(MqttProtocolVersion.V500)
    .WithTlsOptions(o => o.UseTls())
    .WithWillTopic($"{Prefix}/sensor-1/status")
    .WithWillPayload("offline")
    .WithWillRetain(true)
    .WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
    .Build();

await client.ConnectAsync(options, CancellationToken.None);

// Retained "online" status: new subscribers see it immediately.
await client.PublishAsync(new MqttApplicationMessageBuilder()
    .WithTopic($"{Prefix}/sensor-1/status")
    .WithPayload("online")
    .WithRetainFlag(true)
    .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
    .Build());

var rnd = new Random();
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };

while (!cts.IsCancellationRequested)
{
    var reading = new
    {
        deviceId = "sensor-1",
        temperature = Math.Round(20 + rnd.NextDouble() * 5, 2),
        ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
    };

    var message = new MqttApplicationMessageBuilder()
        .WithTopic($"{Prefix}/sensor-1/telemetry")
        .WithPayload(JsonSerializer.Serialize(reading))
        .WithContentType("application/json")
        .WithUserProperty("firmware", "1.4.2")
        .WithMessageExpiryInterval(60)
        .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
        .Build();

    var pub = await client.PublishAsync(message, cts.Token);
    Console.WriteLine($"Published {reading.temperature} -> {pub.ReasonCode}");

    try { await Task.Delay(2000, cts.Token); } catch (TaskCanceledException) { }
}

await client.DisconnectAsync();

Démarrez d'abord le subscriber, puis dotnet run --project Publisher. Le subscriber affiche le JSON, le content type application/json et la user property firmware. Arrêtez le publisher avec Ctrl+C : le statut retained reste online, car la déconnexion était propre. Tuez plutôt le processus (en fermant la fenêtre du terminal) et le broker publie le Last Will, ce qui fait passer le statut retained à offline. C'est l'indicateur de présence fiable le plus simple en MQTT.

Le résultat de PublishAsync contient un reason code pour les QoS 1 et 2. Avec MQTT 5, vous pouvez lire des codes explicites comme QuotaExceeded (0x97) au lieu d'un échec silencieux, ce qui mérite d'être journalisé dans toute application réelle. Pour un rappel de ce que garantit chaque niveau, consultez les niveaux de QoS MQTT expliqués.

Se reconnecter sans le managed client

Comme MQTTnet 5 a supprimé le managed client, la reconnexion vous incombe. Un modèle simple et robuste consiste en une boucle d'arrière-plan qui vérifie IsConnected et utilise TryPingAsync pour détecter les connexions à moitié ouvertes :

async Task KeepConnectedAsync(IMqttClient client, MqttClientOptions options,
    Func<Task> onConnected, CancellationToken ct)
{
    var delay = TimeSpan.FromSeconds(1);
    while (!ct.IsCancellationRequested)
    {
        try
        {
            if (!await client.TryPingAsync(ct))
            {
                await client.ConnectAsync(options, ct);
                await onConnected();          // resubscribe here
                Console.WriteLine("Reconnected");
                delay = TimeSpan.FromSeconds(1);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Connect failed: {ex.Message}");
            delay = TimeSpan.FromSeconds(Math.Min(delay.TotalSeconds * 2, 60));
        }
        await Task.Delay(delay + TimeSpan.FromMilliseconds(Random.Shared.Next(0, 500)), ct);
    }
}

Trois détails comptent ici. D'abord, réabonnez-vous dans onConnected chaque fois que la session n'a pas été reprise ; vérifiez result.IsSessionPresent renvoyé par ConnectAsync si vous utilisez des sessions persistantes. Ensuite, un backoff exponentiel avec jitter évite qu'une flotte de clients ne se reconnecte en même temps après une micro-coupure réseau. Enfin, gardez un client ID stable d'une reconnexion à l'autre si vous comptez sur une session persistante ; générez-le une seule fois au démarrage, pas dans la boucle.

Erreurs MQTTnet courantes et solutions

Symptôme Cause probable Solution
MqttCommunicationTimedOutException à la connexion Port 8883 ou 1883 bloqué par un pare-feu ou un proxy d'entreprise Testez avec Test-NetConnection broker.connectmq.com -Port 8883 ; essayez l'autre port
AuthenticationException pendant la négociation TLS Magasin de certificats racine de l'OS ancien ou horloge système erronée Mettez à jour le trust store de l'OS et synchronisez l'heure ; le broker utilise un certificat Let's Encrypt
La connexion renvoie NotAuthorized / BadUserNameOrPassword Identifiants erronés, ou client ID différent de celui lié aux identifiants Copiez exactement le nom d'utilisateur, le mot de passe et le client ID depuis le portail
Le handler ne se déclenche jamais Handler enregistré après l'abonnement, ou faute de frappe dans le filtre de topic Enregistrez-le avant la connexion ; affichez subResult.Items
Le client se déconnecte toutes les quelques secondes Deux processus utilisent le même client ID Le broker expulse la session la plus ancienne ; rendez les ID uniques
QuotaExceeded (0x97) Limite du plan ou du serveur public atteinte Ralentissez, ou passez à un plan aux limites plus élevées

Tenez compte des limites du serveur public pendant vos tests : 64 Ko par message, 10 messages par seconde par client, 10 abonnements par client et jusqu'à 8 niveaux de topic. Le publisher ci-dessus reste très en deçà.

Passer à un broker privé

Le serveur MQTT public gratuit est idéal pour expérimenter, mais le trafic de production doit passer par une instance privée avec identifiants. Avec un broker MQTT managé de ConnectMQ, vous vous inscrivez, choisissez un plan, créez un broker et recevez un nom d'utilisateur, un mot de passe et un client ID générés. La modification du code tient en trois lignes :

var options = new MqttClientOptionsBuilder()
    .WithTcpServer("YOUR_BROKER_HOST", 8883)
    .WithClientId("YOUR_CLIENT_ID")
    .WithCredentials("YOUR_USERNAME", "YOUR_PASSWORD")
    .WithProtocolVersion(MqttProtocolVersion.V500)
    .WithTlsOptions(o => o.UseTls())
    .Build();

Gardez les identifiants hors du code source : lisez-les depuis des variables d'environnement, dotnet user-secrets en développement, ou le gestionnaire de secrets de votre plateforme en production. Sur ConnectMQ, les client ID sont liés aux identifiants émis : un mot de passe divulgué ne suffit donc pas à usurper un autre client, mais vous devez tout de même renouveler tout ce qui finit dans un dépôt.

Le plan Serverless gratuit autorise jusqu'à 25 connexions, une QoS jusqu'à 1 et des messages de 256 Ko, ce qui est largement suffisant pour le développement. Le Serverless payant monte à 1 000 connexions avec QoS 2, des messages de 1 Mo et les shared subscriptions, et Dedicated vous offre un cluster isolé.

Aller plus loin avec MQTTnet

Une fois les applications console fonctionnelles, le même client s'intègre dans des applications .NET plus vastes :

  1. Hébergez le subscriber comme service longue durée avec injection de dépendances : consultez le guide du service d'arrière-plan MQTT avec ASP.NET Core.
  2. Créez une interface mobile et desktop multiplateforme avec le tutoriel application MQTT .NET MAUI.
  3. Pilotez des scènes 3D à partir de données en direct avec un client MQTT Unity.
  4. Comparez MQTTnet aux bibliothèques d'autres langages dans le panorama des bibliothèques client MQTT.

Si vous hésitez encore sur le broker auquel ces clients doivent se connecter, le guide pour choisir un broker MQTT cloud explique les compromis entre auto-hébergement et service hébergé.

Démarrer avec ConnectMQ

Votre code MQTTnet est prêt pour la production dès qu'il pointe vers un broker privé. Créez-en un sur app.connectmq.com avec le plan Serverless gratuit, sans carte bancaire, copiez les identifiants générés dans l'options builder, et regardez vos messages arriver dans l'explorateur de topics en direct du portail et dans son client MQTT intégré au navigateur.

Questions fréquentes

MQTTnet est-il gratuit pour un usage commercial ?

Oui. MQTTnet est open source sous licence MIT et maintenu au sein de la .NET Foundation : vous pouvez donc l'utiliser dans des produits commerciaux.

MQTTnet prend-il en charge MQTT 5 ?

Oui. Définissez WithProtocolVersion(MqttProtocolVersion.V500) pour utiliser les fonctionnalités MQTT 5 comme les user properties, le content type, l'expiration des messages, les reason codes et l'expiration de session. ConnectMQ prend en charge MQTT 3.1.1 et 5.0.

Qu'est devenu le managed client de MQTTnet ?

Il a été supprimé dans MQTTnet 5. Utilisez une boucle de reconnexion comme celle de ce tutoriel, ou restez sur MQTTnet 4.x si vous dépendez fortement du managed client et ne pouvez pas encore migrer.

MQTTnet peut-il se connecter via WebSocket ?

Oui. Utilisez WithWebSocketServer(o => o.WithUri("wss://broker.connectmq.com:8084/mqtt")) au lieu de WithTcpServer. C'est utile lorsque seul le trafic HTTPS est autorisé en sortie d'un réseau.

Articles liés

Les meilleurs outils CLI MQTT pour tester, scripter et déboguer

Un comparatif pratique de mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, la CLI MQTT.js, curl et mqttui, avec le même test exécuté sur le serveur MQTT public gratuit broker.connectmq.com pour choisir le bon outil pour le scripting et le débogage.

9 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.