Implementação de Broker e Cliente MQTT com .NET Core 3.0

O MQTT (Message Queuing Telemetry Transport) é um protocolo de mensagens leve, projetado para redes com pouca largura de banda e latência elevada. Sua arquitetura publicação/assinatura permite que dispositivos troquem dados de forma assíncrona, o que o torna adequado para Internet das Coisas (IoT), sensores, automação e aplicações móveis. Quando combinado ao .NET Core 3.0, o MQTT pode ser utilizado para criar servidores e clientes multiplataforma de forma simplificada.

Fundamentso do Protocolo MQTT

Um sistema MQTT é composto por três elementos principais. O cliente é qualquer dispositivo ou aplicação capaz de publicar ou receber mensagens. O broker é responsável por receber as mensagens publicadas e encaminhá-las aos assinantes dos tópicos correspondentes. Os tópicos são strings hierárquicas que organizam as mensagens, como predio/andar1/sala/temperatura.

O protocolo oferece três níveis de qualidade de serviço (QoS). O nível 0 entrega a mensagem no máximo uma vez, sem confirmação. O nível 1 garante que a mensagem seja entregue pelo menos uma vez, podendo gerar duplicatas. O nível 2 garante entrega única, sendo o mais robusto, porém com maior custo de comunicação.

Configuração do Projeto

Para utilizar MQTT no .NET Core 3.0, a forma mais prática é adicionar o pacote MQTTnet por meio do NuGet. No arquivo .csproj, inclua a referência:

<ItemGroup>
  <PackageReference Include="MQTTnet" Version="3.1.2" />
</ItemGroup>

Também é possível instalar via CLI:

dotnet add package MQTTnet --version 3.1.2

Construindo um Broker MQTT

O MQTTnet permite iniciar um broker embarcado dentro da própria aplicação .NET Core. A seguir, uma classe que encapsula o servidor e registra o evento de conexão de clientes:

using MQTTnet;
using MQTTnet.Server;
using System;
using System.Threading.Tasks;

public class BrokerLocal
{
    private IMqttServer _servidor;

    public async Task Iniciar(int porta = 1883)
    {
        var fabrica = new MqttFactory();
        var opcoes = new MqttServerOptionsBuilder()
            .WithDefaultEndpoint()
            .WithDefaultEndpointPort(porta)
            .Build();

        _servidor = fabrica.CreateMqttServer();

        _servidor.UseClientConnectedHandler(args =>
        {
            Console.WriteLine($"Conexão recebida: {args.ClientId}");
        });

        await _servidor.StartAsync(opcoes);
        Console.WriteLine($"Broker ativo na porta {porta}");
    }

    public async Task Parar()
    {
        if (_servidor != null)
        {
            await _servidor.StopAsync();
        }
    }
}

Essa abordagem é útil para testes locais, ambientes de desenvolvimento e cenários em que é desejável manter o broker junto à aplicação, sem depender de instalações externas.

Implementando o Cliente MQTT

O cleinte é responsável por se conectar ao broker, inscrever-se em tópicos e publicar mensagens. A classe a seguir encapsula essas operações:

using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using System;
using System.Text;
using System.Threading.Tasks;

public class ClienteMqtt : IDisposable
{
    private readonly IMqttClient _cliente;
    private readonly IMqttClientOptions _opcoes;

    public ClienteMqtt(string identificador, string host, int porta)
    {
        var fabrica = new MqttFactory();
        _cliente = fabrica.CreateMqttClient();

        _opcoes = new MqttClientOptionsBuilder()
            .WithClientId(identificador)
            .WithTcpServer(host, porta)
            .WithCleanSession(true)
            .Build();
    }

    public async Task ConectarAsync()
    {
        await _cliente.ConnectAsync(_opcoes);

        _cliente.UseApplicationMessageReceivedHandler(msg =>
        {
            var conteudo = Encoding.UTF8.GetString(msg.ApplicationMessage.Payload);
            Console.WriteLine($"[{msg.ApplicationMessage.Topic}] {conteudo}");
        });
    }

    public async Task PublicarAsync(
        string topico,
        string dados,
        int qos = 0,
        bool reter = false)
    {
        var mensagem = new MqttApplicationMessageBuilder()
            .WithTopic(topico)
            .WithPayload(Encoding.UTF8.GetBytes(dados))
            .WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
            .WithRetainFlag(reter)
            .Build();

        await _cliente.PublishAsync(mensagem);
    }

    public async Task InscreverAsync(string filtro)
    {
        var topico = new MqttTopicFilterBuilder()
            .WithTopic(filtro)
            .Build();

        await _cliente.SubscribeAsync(topico);
    }

    public async Task ConectarComRetentativaAsync()
    {
        while (!_cliente.IsConnected)
        {
            try
            {
                await _cliente.ConnectAsync(_opcoes);
            }
            catch
            {
                await Task.Delay(TimeSpan.FromSeconds(5));
            }
        }
    }

    public void Dispose()
    {
        _cliente?.Dispose();
    }
}

Tópicos, Filtros e Qualidade de Serviço

Os tópicos podem ser organizados em níveis separados por barras. Para criar assinaturas flexíveis, o MQTT define dois curingas. O curinga + substitui um único nível, enquanto # substitui um ou mais níveis, desde que apareça no final do filtro.

Por exemplo, o filtro fabrica/+/temperatura recebe fabrica/setorA/temperatura e fabrica/setorB/temperatura, mas não fabrica/setorA/umidade. Já fabrica/setorA/# recebe qualquer mensagem sob fabrica/setorA e seus subníveis.

A escolha do QoS depende do cenário. Mensagens de telemetria periódicas podem usar QoS 0, enquanto comandos e alertas críticos devem usar QoS 1 ou 2. O flag Retain pode ser utilizado para que o broker armazene a última mensagem de um tópico e a entregue a novos assinantes assim que eles se conectarem.

Exemplo Completo de Uso

O trecho a seguir demonstra como iniciar o broker, conectar um publicador e um assinante, e trocar mensagens no mesmo processo:

using System;
using System.Threading.Tasks;

class Programa
{
    static async Task Main(string[] args)
    {
        var broker = new BrokerLocal();
        await broker.Iniciar(1883);

        var publicador = new ClienteMqtt("pub-01", "127.0.0.1", 1883);
        await publicador.ConectarAsync();

        var assinante = new ClienteMqtt("sub-01", "127.0.0.1", 1883);
        await assinante.ConectarAsync();
        await assinante.InscreverAsync("sensores/temperatura/#");

        await publicador.PublicarAsync(
            "sensores/temperatura/sala",
            "22.8",
            qos: 1,
            reter: false);

        Console.WriteLine("Pressione qualquer tecla para encerrar.");
        Console.ReadKey();

        publicador.Dispose();
        assinante.Dispose();
        await broker.Parar();
    }
}

Com essa estrutura, é possível expandir a solução para incluir autenticação por usuário e senha, conexão TLS, persistência de mensagens e integração com outras camadas da aplicação, como controladores ASP.NET Core ou serviços em segundo plano.

Tags: MQTT .NET Core 3.0 MQTTnet C# IoT

Publicado em 7-12 23:46