Tutoriais de IoT

Tutorial MQTTnet em C#: Crie Clientes MQTT 5 em .NET com TLS

Crie um publisher e um subscriber de console em .NET com MQTTnet, propriedades do MQTT 5 e TLS na porta 8883, depois adicione lógica de reconexão e leve o mesmo código para um broker privado da ConnectMQ.

Editor de código C# mostrando um cliente mqttnet publicando dados de sensores em um broker MQTT na nuvem
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

O MQTTnet é a biblioteca MQTT mais usada no .NET e atende desde um teste rápido no console até um serviço de produção. Neste tutorial você vai criar um publisher e um subscriber em C# com MQTTnet, usar propriedades do MQTT 5, criptografar a conexão com TLS e adicionar uma lógica de reconexão confiável. Todos os exemplos rodam no servidor MQTT público (gratuito) broker.connectmq.com, então você pode acompanhar sem instalar nada além do .NET SDK.

O que você precisa

  • .NET 8 SDK ou mais recente (dotnet --version para verificar)
  • Qualquer editor: Visual Studio, Rider ou VS Code com a extensão C#
  • Um terminal com acesso de saída à porta 8883 (TLS) ou 1883 (TCP simples)

Você não precisa de conta para a primeira parte. O servidor MQTT público em broker.connectmq.com aceita clientes anônimos com qualquer client ID não vazio e único. Lembre-se de que a área pública pode ser lida por qualquer pessoa, então use um prefixo de topic aleatório e nunca envie dados reais para lá.

Versões do MQTTnet: leia isto primeiro

O MQTTnet passou por uma grande reorganização da API na versão 5. Se você copiar trechos de posts antigos, vai esbarrar em erros de compilação. As diferenças mais importantes:

Assunto MQTTnet 4.x MQTTnet 5.x
Classe factory MqttFactory MqttClientFactory
Namespaces MQTTnet.Client, MQTTnet Em geral unificados em MQTTnet
Managed client (reconexão automática) MQTTnet.Extensions.ManagedClient Removido, escreva seu próprio loop
Tipo do payload byte[] / ArraySegment<byte> ReadOnlySequence<byte>
Configuração de TLS WithTls() (obsoleto) ou WithTlsOptions WithTlsOptions(o => o.UseTls())

Este guia usa o MQTTnet 5. Os conceitos valem para o 4.x, mas os nomes das classes mudam conforme mostrado acima. O repositório oficial do MQTTnet tem exemplos de cada recurso, caso você queira se aprofundar.

Criando os projetos

Crie uma solution com dois apps de 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

Escolha agora um prefixo de topic exclusivo e use-o nos dois apps. Algo como connectmq/demo/k7f3q9/ serve. Colisões em um broker compartilhado são uma fonte clássica da confusão "por que estou recebendo mensagens que nunca enviei?".

Seu primeiro subscriber com MQTTnet

Substitua o Subscriber/Program.cs pelo código abaixo. Ele se conecta via TLS na porta 8883, usa MQTT 5, assina com QoS 1 e imprime cada mensagem com suas propriedades do 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());

Execute com dotnet run --project Subscriber. Você deve ver Connected: Success e um QoS concedido para a assinatura. Observe que o handler é registrado antes do ConnectAsync; se você assinar com uma sessão persistente, as mensagens podem chegar no instante em que a conexão abre, e um handler registrado tarde as perderia.

Publicando com MQTTnet e propriedades do MQTT 5

Agora o Publisher/Program.cs. Ele envia uma leitura em JSON a cada dois segundos com content type, uma user property e um intervalo de expiração de mensagem, além de uma mensagem de status retida.

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

// Status "online" retido: novos assinantes o veem imediatamente.
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();

Inicie primeiro o subscriber e depois dotnet run --project Publisher. O subscriber imprime o JSON, o content type application/json e a user property firmware. Pare o publisher com Ctrl+C e o status retido continua online, porque a desconexão foi limpa. Se, em vez disso, você matar o processo (fechando a janela do terminal), o broker publica o Last Will e muda o status retido para offline. Esse padrão é o indicador de presença confiável mais simples do MQTT.

O resultado do PublishAsync contém um reason code para QoS 1 e 2. Com MQTT 5 você pode ler códigos significativos como QuotaExceeded (0x97) em vez de uma falha silenciosa, o que vale registrar em log em qualquer aplicação real. Se quiser relembrar o que cada nível garante, veja níveis de QoS do MQTT explicados.

Reconectando sem o managed client

Como o MQTTnet 5 removeu o managed client, a reconexão fica por sua conta. Um padrão pequeno e robusto é um loop em segundo plano que verifica IsConnected e usa TryPingAsync para detectar conexões semiabertas:

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();          // assine novamente aqui
                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);
    }
}

Três detalhes importam aqui. Primeiro, assine novamente em onConnected sempre que a sessão não tiver sido retomada; confira result.IsSessionPresent retornado pelo ConnectAsync se você usa sessões persistentes. Segundo, o back-off exponencial com jitter evita que uma frota de clientes reconecte toda ao mesmo tempo após uma oscilação de rede. Terceiro, mantenha o client ID estável entre as reconexões se você depende de uma sessão persistente; gere-o uma vez na inicialização, não dentro do loop.

Erros comuns do MQTTnet e como corrigir

Sintoma Causa provável Correção
MqttCommunicationTimedOutException ao conectar Porta 8883 ou 1883 bloqueada por firewall ou proxy corporativo Teste com Test-NetConnection broker.connectmq.com -Port 8883; tente a outra porta
AuthenticationException durante o handshake TLS Repositório raiz do SO desatualizado ou relógio do sistema errado Atualize o trust store do SO e sincronize a hora; o broker usa um certificado Let's Encrypt
O connect retorna NotAuthorized / BadUserNameOrPassword Credenciais erradas ou client ID diferente do vinculado às credenciais Copie usuário, senha e client ID exatamente como estão no portal
O handler nunca dispara Handler registrado depois da assinatura ou erro de digitação no filtro de topic Registre antes de conectar; imprima subResult.Items
O cliente desconecta a cada poucos segundos Dois processos usando o mesmo client ID O broker derruba a sessão mais antiga; torne os IDs únicos
QuotaExceeded (0x97) Limite do plano ou do servidor MQTT público atingido Reduza o ritmo ou migre para um plano com limites maiores

Fique atento aos limites do servidor MQTT público durante os testes: 64 KB por mensagem, 10 mensagens por segundo por cliente, 10 assinaturas por cliente e até 8 níveis de topic. O publisher acima fica muito abaixo deles.

Migrando para um broker privado

O servidor MQTT público é ideal para experimentos e protótipos, mas o tráfego de produção, como a telemetria das câmaras frias de um frigorífico ou das bombas de uma estação de saneamento, deve ir para uma instância privada com credenciais. Com um broker MQTT gerenciado da ConnectMQ, você se cadastra, escolhe um plano, cria um broker e recebe usuário, senha e client ID gerados. A mudança no código é de três linhas:

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

Mantenha as credenciais fora do código-fonte: leia-as de variáveis de ambiente, do dotnet user-secrets durante o desenvolvimento ou do cofre de segredos da sua plataforma em produção. Na ConnectMQ, os client IDs são vinculados às credenciais emitidas, então uma senha vazada sozinha não basta para se passar por outro cliente, mas você ainda deve trocar qualquer coisa que acabe em um repositório.

O plano Serverless (Free) permite até 25 conexões, QoS até 1 e mensagens de 256 KB, o que é suficiente para desenvolvimento. O Serverless (Paid) eleva isso para 1.000 conexões com QoS 2, mensagens de 1 MB e shared subscriptions, e o Dedicated oferece um cluster isolado.

Próximos passos com o MQTTnet

Quando os apps de console estiverem funcionando, o mesmo cliente se encaixa em aplicações .NET maiores:

  1. Hospede o subscriber como um serviço de longa duração com injeção de dependência: veja o guia de background service MQTT no ASP.NET Core.
  2. Crie uma interface multiplataforma para celular e desktop com o tutorial de app MQTT com .NET MAUI.
  3. Controle cenas 3D com dados em tempo real usando um cliente MQTT no Unity.
  4. Compare o MQTTnet com bibliotecas de outras linguagens na visão geral de bibliotecas de cliente MQTT.

Se você ainda está decidindo onde esses clientes devem se conectar, o guia para escolher um broker MQTT na nuvem explica os trade-offs entre hospedar por conta própria e usar um serviço hospedado.

Comece a usar a ConnectMQ

Seu código MQTTnet está pronto para produção assim que aponta para um broker privado. Crie um em app.connectmq.com no plano Serverless (Free), sem cartão de crédito, copie as credenciais geradas para o options builder e veja suas mensagens chegarem ao explorador de topics em tempo real e ao cliente MQTT no navegador do portal.

Perguntas frequentes

O MQTTnet é gratuito para uso comercial?

Sim. O MQTTnet é open source sob a licença MIT e é mantido sob a .NET Foundation, então você pode usá-lo em produtos comerciais.

O MQTTnet suporta MQTT 5?

Sim. Defina WithProtocolVersion(MqttProtocolVersion.V500) para usar recursos do MQTT 5 como user properties, content type, expiração de mensagem, reason codes e expiração de sessão. A ConnectMQ suporta tanto MQTT 3.1.1 quanto 5.0.

O que aconteceu com o managed client do MQTTnet?

Ele foi removido no MQTTnet 5. Use um loop de reconexão como o deste tutorial ou continue no MQTTnet 4.x se você depende muito do managed client e ainda não pode migrar.

O MQTTnet consegue se conectar via WebSocket?

Sim. Use WithWebSocketServer(o => o.WithUri("wss://broker.connectmq.com:8084/mqtt")) em vez de WithTcpServer. Isso ajuda quando apenas tráfego HTTPS tem permissão para sair de uma rede.

Artigos relacionados

Melhores Ferramentas CLI MQTT para Testes, Scripts e Depuração

Uma comparação prática entre mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, a CLI do MQTT.js, curl e mqttui, com o mesmo teste rodando no broker.connectmq.com, para você escolher a ferramenta certa para scripts e depuração.

9 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.