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.