IoT-Tutorials

MQTTnet C# Tutorial: MQTT-5-Clients in .NET mit TLS entwickeln

Entwickeln Sie mit MQTTnet einen .NET-Konsolen-Publisher und -Subscriber mit MQTT-5-Properties und TLS auf Port 8883, ergänzen Sie Reconnect-Logik und übertragen Sie denselben Code auf einen privaten ConnectMQ-Broker.

C#-Code-Editor mit einem MQTTnet-Client, der Sensordaten an einen Cloud-MQTT-Broker veröffentlicht
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

MQTTnet ist die meistgenutzte MQTT-Bibliothek für .NET und deckt alles ab – vom schnellen Konsolentest bis zum Produktivservice. In diesem Tutorial entwickeln Sie mit MQTTnet einen C#-Publisher und einen Subscriber, nutzen MQTT-5-Properties, verschlüsseln die Verbindung mit TLS und ergänzen eine Reconnect-Logik, auf die Sie sich verlassen können. Alle Beispiele laufen gegen den kostenlosen öffentlichen Broker broker.connectmq.com, sodass Sie außer dem .NET SDK nichts installieren müssen. In der Industrie ist .NET weit verbreitet, etwa für Leitstand-Anwendungen, MES-Erweiterungen und OPC-UA-Clients; MQTTnet bindet solche Anwendungen an einen Unified Namespace (UNS) auf dem Broker an.

Was Sie brauchen

  • .NET 8 SDK oder neuer (dotnet --version zum Prüfen)
  • Einen beliebigen Editor: Visual Studio, Rider oder VS Code mit der C#-Erweiterung
  • Ein Terminal mit ausgehendem Zugriff auf Port 8883 (TLS) oder 1883 (unverschlüsseltes TCP)

Für den ersten Teil brauchen Sie kein Konto. Der öffentliche MQTT-Broker unter broker.connectmq.com ist für Tests und Prototypen gedacht und akzeptiert anonyme Clients mit jeder nicht leeren, eindeutigen Client ID. Denken Sie daran, dass der öffentliche Bereich für alle lesbar ist: Verwenden Sie ein zufälliges Topic-Präfix und senden Sie dort niemals echte Daten.

MQTTnet-Versionen: Bitte zuerst lesen

MQTTnet hat in Version 5 eine große API-Bereinigung erhalten. Wenn Sie Snippets aus älteren Blogbeiträgen kopieren, stoßen Sie auf Compilerfehler. Die wichtigsten Unterschiede:

Thema MQTTnet 4.x MQTTnet 5.x
Factory-Klasse MqttFactory MqttClientFactory
Namespaces MQTTnet.Client, MQTTnet Größtenteils in MQTTnet zusammengeführt
Managed Client (Auto-Reconnect) MQTTnet.Extensions.ManagedClient Entfernt, eigene Schleife schreiben
Payload-Typ byte[] / ArraySegment<byte> ReadOnlySequence<byte>
TLS-Einrichtung WithTls() (veraltet) oder WithTlsOptions WithTlsOptions(o => o.UseTls())

Dieser Leitfaden zielt auf MQTTnet 5. Die Konzepte gelten auch für 4.x, die Klassennamen unterscheiden sich jedoch wie oben gezeigt. Das offizielle MQTTnet-Repository enthält Beispiele zu jeder Funktion, falls Sie tiefer einsteigen möchten.

Die Projekte anlegen

Legen Sie eine Solution mit zwei Konsolen-Apps an:

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

Wählen Sie jetzt ein eindeutiges Topic-Präfix und verwenden Sie es in beiden Apps. Etwas wie connectmq/demo/k7f3q9/ genügt. Kollisionen auf einem geteilten Broker sind eine klassische Ursache für die Frage „Warum empfange ich Nachrichten, die ich nie gesendet habe?“.

Ihr erster MQTTnet-Subscriber

Ersetzen Sie Subscriber/Program.cs durch den folgenden Code. Er verbindet sich per TLS auf Port 8883, nutzt MQTT 5, abonniert mit QoS 1 und gibt jede Nachricht samt ihren MQTT-5-Properties aus.

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());

Starten Sie ihn mit dotnet run --project Subscriber. Sie sollten Connected: Success und eine gewährte QoS für die Subscription sehen. Beachten Sie, dass der Handler vor ConnectAsync registriert wird: Wenn Sie mit einer persistenten Session abonnieren, können Nachrichten in dem Moment eintreffen, in dem die Verbindung steht, und ein später registrierter Handler würde sie verpassen.

Mit MQTTnet und MQTT-5-Properties veröffentlichen

Nun zu Publisher/Program.cs. Es sendet alle zwei Sekunden einen JSON-Messwert mit Content Type, User Property und Message-Expiry-Intervall sowie eine Retained-Statusnachricht.

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-Status "online": neue Subscriber sehen ihn sofort.
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();

Starten Sie zuerst den Subscriber, dann dotnet run --project Publisher. Der Subscriber gibt das JSON, den Content Type application/json und die User Property firmware aus. Beenden Sie den Publisher mit Strg+C, bleibt der Retained-Status online, weil die Trennung sauber war. Beenden Sie den Prozess stattdessen hart (Terminalfenster schließen), veröffentlicht der Broker den Last Will und setzt den Retained-Status auf offline. Dieses Muster ist der einfachste zuverlässige Präsenzindikator in MQTT.

Das Ergebnis von PublishAsync enthält bei QoS 1 und 2 einen Reason Code. Mit MQTT 5 erhalten Sie aussagekräftige Codes wie QuotaExceeded (0x97) statt eines stillen Fehlschlags – das sollten Sie in jeder echten Anwendung protokollieren. Eine Auffrischung, was jede Stufe garantiert, bietet MQTT-QoS-Stufen erklärt.

Reconnect ohne den Managed Client

Da MQTTnet 5 den Managed Client entfernt hat, sind Sie selbst für Reconnects zuständig. Ein kleines, robustes Muster ist eine Hintergrundschleife, die IsConnected prüft und mit TryPingAsync halboffene Verbindungen erkennt:

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();          // hier neu abonnieren
                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);
    }
}

Drei Details sind hier entscheidend. Erstens: Abonnieren Sie in onConnected neu, wann immer die Session nicht fortgesetzt wurde; prüfen Sie result.IsSessionPresent aus ConnectAsync, wenn Sie persistente Sessions nutzen. Zweitens: Exponentielles Backoff mit Jitter verhindert, dass sich eine ganze Client-Flotte nach einer Netzwerkstörung im Gleichschritt neu verbindet. Drittens: Halten Sie die Client ID über Reconnects hinweg stabil, wenn Sie auf eine persistente Session setzen; erzeugen Sie sie einmal beim Start, nicht innerhalb der Schleife.

Häufige MQTTnet-Fehler und Lösungen

Symptom Wahrscheinliche Ursache Lösung
MqttCommunicationTimedOutException beim Verbinden Port 8883 oder 1883 von Firewall oder Unternehmensproxy blockiert Mit Test-NetConnection broker.connectmq.com -Port 8883 testen; den anderen Port versuchen
AuthenticationException beim TLS-Handshake Veralteter Root-Store des Betriebssystems oder falsche Systemzeit Truststore aktualisieren und Uhrzeit synchronisieren; der Broker nutzt ein Let's-Encrypt-Zertifikat
Connect liefert NotAuthorized / BadUserNameOrPassword Falsche Zugangsdaten oder nicht die an die Zugangsdaten gebundene Client ID Benutzername, Passwort und Client ID exakt aus dem Portal kopieren
Handler wird nie aufgerufen Handler nach dem Subscribe registriert oder Tippfehler im Topic-Filter Vor dem Connect registrieren; subResult.Items ausgeben
Client trennt sich alle paar Sekunden Zwei Prozesse mit derselben Client ID Der Broker wirft die ältere Session hinaus; IDs eindeutig machen
QuotaExceeded (0x97) Limit des Plans oder des öffentlichen Brokers erreicht Langsamer senden oder in einen Plan mit höheren Limits wechseln

Beachten Sie beim Testen die Limits des öffentlichen Brokers: 64 KB pro Nachricht, 10 Nachrichten pro Sekunde und Client, 10 Subscriptions pro Client und bis zu 8 Topic-Ebenen. Der obige Publisher bleibt weit darunter.

Wechsel zu einem privaten Broker

Der öffentliche Broker ist ideal für Experimente, Produktivverkehr gehört jedoch auf eine private Instanz mit Zugangsdaten. Mit einem verwalteten MQTT-Broker von ConnectMQ registrieren Sie sich, wählen einen Plan, erstellen einen Broker und erhalten generierten Benutzernamen, Passwort und Client ID. Die Codeänderung umfasst drei Zeilen:

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();

Halten Sie Zugangsdaten aus dem Quellcode heraus: Lesen Sie sie aus Umgebungsvariablen, während der Entwicklung aus dotnet user-secrets oder im Produktivbetrieb aus dem Secret Store Ihrer Plattform. Client IDs sind bei ConnectMQ an die ausgestellten Zugangsdaten gebunden, sodass ein geleaktes Passwort allein nicht genügt, um sich als anderer Client auszugeben – trotzdem sollten Sie alles rotieren, was in einem Repository landet.

Der kostenlose Serverless-Plan erlaubt bis zu 25 Verbindungen, QoS bis 1 und Nachrichten bis 256 KB – für die Entwicklung mehr als genug. Der kostenpflichtige Serverless-Plan erhöht das auf 1.000 Verbindungen mit QoS 2, Nachrichten bis 1 MB und Shared Subscriptions, und Dedicated bietet Ihnen einen isolierten Cluster.

Wie es mit MQTTnet weitergeht

Sobald die Konsolen-Apps laufen, passt derselbe Client in größere .NET-Anwendungen:

  1. Betreiben Sie den Subscriber als langlebigen Service mit Dependency Injection: siehe den Leitfaden zum ASP.NET-Core-MQTT-Background-Service.
  2. Bauen Sie mit dem Tutorial zur .NET-MAUI-MQTT-App eine plattformübergreifende Mobil- und Desktop-Oberfläche.
  3. Steuern Sie 3D-Szenen mit Live-Daten über einen Unity MQTT Client.
  4. Vergleichen Sie MQTTnet mit Bibliotheken anderer Sprachen in der Übersicht der MQTT-Client-Bibliotheken.

Wenn Sie noch entscheiden, wohin sich diese Clients verbinden sollen, erläutert der Leitfaden zur Auswahl eines Cloud-MQTT-Brokers die Vor- und Nachteile von selbst betrieben vs. verwaltet.

Starten Sie mit ConnectMQ

Ihr MQTTnet-Code ist produktionsreif, sobald er auf einen privaten Broker zeigt. Erstellen Sie einen unter app.connectmq.com im kostenlosen Serverless-Plan, ganz ohne Kreditkarte, kopieren Sie die generierten Zugangsdaten in den Options-Builder und sehen Sie zu, wie Ihre Nachrichten im Live-Topic-Explorer und im MQTT-Client im Browser des Portals ankommen.

Häufig gestellte Fragen

Ist MQTTnet für die kommerzielle Nutzung kostenlos?

Ja. MQTTnet ist Open Source unter der MIT-Lizenz und wird unter dem Dach der .NET Foundation gepflegt, sodass Sie es in kommerziellen Produkten einsetzen können.

Unterstützt MQTTnet MQTT 5?

Ja. Setzen Sie WithProtocolVersion(MqttProtocolVersion.V500), um MQTT-5-Funktionen wie User Properties, Content Type, Message Expiry, Reason Codes und Session Expiry zu nutzen. ConnectMQ unterstützt sowohl MQTT 3.1.1 als auch 5.0.

Was ist mit dem MQTTnet Managed Client passiert?

Er wurde in MQTTnet 5 entfernt. Verwenden Sie eine Reconnect-Schleife wie in diesem Tutorial oder bleiben Sie bei MQTTnet 4.x, wenn Sie stark vom Managed Client abhängen und noch nicht migrieren können.

Kann sich MQTTnet über WebSocket verbinden?

Ja. Verwenden Sie WithWebSocketServer(o => o.WithUri("wss://broker.connectmq.com:8084/mqtt")) statt WithTcpServer. Das hilft, wenn aus einem Netzwerk nur HTTPS-Verkehr nach außen erlaubt ist.

Verwandte Artikel

Die besten MQTT-CLI-Tools zum Testen, Skripten und Debuggen

Ein praxisnaher Vergleich von mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, der MQTT.js-CLI, curl und mqttui – mit demselben Test gegen broker.connectmq.com, damit Sie das richtige Tool zum Skripten und Debuggen wählen.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.