一、MQTT 核心概念
1. 什么是 MQTT?
MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布 / 订阅模式的物联网通信协议,由 IBM 开发,专为低带宽、高延迟、不可靠网络设计(如物联网设备、传感器、移动网络)。
核心特点:
- 轻量级:协议头最小仅 2 字节,占用资源极少
- 发布 / 订阅模式:解耦消息发送者(发布者)和接收者(订阅者)
- QoS 消息质量:支持 3 级消息可靠性保障
- 持久会话:客户端断开后可恢复会话,避免消息丢失
- 遗嘱消息:客户端异常断开时,代理自动发送预设消息
2. MQTT 核心角色
|-----------------|-------------------------------|
| 角色 | 说明 |
| 发布者(Publisher) | 发送消息的客户端(如传感器、APP),无需知道谁接收消息 |
| 订阅者(Subscriber) | 接收消息的客户端(如服务器、APP),只需订阅感兴趣的主题 |
| 代理(Broker) | 消息中间件(服务器),负责接收发布者消息并转发给订阅者 |
3. 核心术语
(1)主题(Topic)
消息的 "地址",订阅者通过主题筛选接收的消息,格式类似文件路径:
- 示例:
sensor/temperature/room1、device/light/control - 通配符:
+:匹配单层主题(如sensor/+/room1匹配sensor/temperature/room1)#:匹配多层主题(如sensor/#匹配sensor/temperature/room1、sensor/humidity/room2)
(2)QoS(Quality of Service)
消息传输质量等级,共 3 级:
-
QoS 0:最多一次(尽力交付,消息可能丢失,适用于非关键数据如温湿度)
-
QoS 1:至少一次(确保消息到达,可能重复,适用于控制指令)
-
QoS 2 :恰好一次(确保消息仅送达一次,适用于金融、计费等关键数据)
(3)保留消息(Retained Message)
Broker 为主题保存最后一条保留消息,新订阅者上线时立即收到该消息(如设备状态)。
(4)遗嘱消息(Last Will and Testament, LWT)
客户端连接时预设的消息,若客户端异常断开(如断电),Broker 自动将该消息发布到指定主题(如设备离线通知)。
二、C# 实现 MQTT 实战
1. 环境准备
(1)依赖库
C# 开发 MQTT 最常用的库是 MQTTnet(.NET 官方推荐,跨平台),支持 .NET Framework/.NET Core/.NET 5+。
安装方式(NuGet 包管理器):
cs
# 安装核心库
Install-Package MQTTnet
# 安装客户端库(推荐)
Install-Package MQTTnet.Extensions.ManagedClient
(2)MQTT Broker 准备
需要一个 MQTT 代理服务器,推荐 2 种方式:
- 本地部署:Eclipse Mosquitto(轻量级,免费)
- 在线测试:EMQ X 公共服务器(
broker.emqx.io,端口 1883)
2. C# 代码实战
案例包含 MQTT 客户端发布消息 和 订阅消息 两个核心功能,基于 .NET 6 编写
第一步:通用配置(常量定义)
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTT示例
{
public static class MqttConfig
{
// EMQ X 公共测试服务器(无需部署,直接用)
public const string BrokerAddress = "broker.emqx.io";
public const int BrokerPort = 1883;
// 客户端ID(必须唯一,建议加随机数避免冲突)
public static string PublisherClientId => $"csharp_publisher_{Guid.NewGuid():N}";
public static string SubscriberClientId => $"csharp_subscriber_{Guid.NewGuid():N}";
// 测试主题
public const string TestTopic = "csharp/mqtt/demo";
// 用户名/密码(公共服务器无需验证,若用私有Broker需配置)
public const string Username = "";
public const string Password = "";
}
}
第二步:订阅者(Subscriber)实现(接收消息)
cs
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTT示例
{
public class MqttSubscriber
{
private IMqttClient _mqttClient;
/// <summary>
/// 启动订阅者
/// </summary>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
// 1. 创建MQTT客户端工厂
var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient();
// 2. 配置客户端选项
var options = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.BrokerAddress, MqttConfig.BrokerPort) // 指定Broker地址和端口
.WithClientId(MqttConfig.SubscriberClientId) // 客户端ID
.WithCredentials(MqttConfig.Username, MqttConfig.Password) // 用户名密码(可选)
.WithCleanSession(false) // 非清理会话(支持持久会话)
.WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) // 遗嘱消息QoS
.WithWillTopic("csharp/mqtt/status") // 遗嘱消息主题
.WithWillPayload(Encoding.UTF8.GetBytes("Subscriber offline")) // 遗嘱消息内容
.WithWillRetain(true) // 遗嘱消息设为保留消息
.Build();
// 3. 注册消息接收事件(核心:收到消息时触发)
_mqttClient.ApplicationMessageReceivedAsync += e =>
{
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
Console.WriteLine($"\n【订阅者收到消息】");
Console.WriteLine($"主题:{e.ApplicationMessage.Topic}");
Console.WriteLine($"内容:{payload}");
Console.WriteLine($"QoS:{e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"保留消息:{e.ApplicationMessage.Retain}");
return Task.CompletedTask;
};
// 4. 注册连接状态变更事件
_mqttClient.DisconnectedAsync += e =>
{
Console.WriteLine($"\n【订阅者断开连接】原因:{e.Reason}");
return Task.CompletedTask;
};
// 5. 连接到Broker
await _mqttClient.ConnectAsync(options, cancellationToken);
Console.WriteLine($"【订阅者已连接】ClientId:{MqttConfig.SubscriberClientId}");
// 6. 订阅指定主题
var subscribeOptions = factory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f =>
{
f.WithTopic(MqttConfig.TestTopic); // 订阅的主题
f.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce); // 订阅QoS
})
.Build();
var subscribeResult = await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
Console.WriteLine($"【订阅主题成功】{MqttConfig.TestTopic},返回码:{subscribeResult.Items.First().ResultCode}");
}
/// <summary>
/// 停止订阅者
/// </summary>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
if (_mqttClient.IsConnected)
{
// 取消订阅
await _mqttClient.UnsubscribeAsync(MqttConfig.TestTopic, cancellationToken);
// 断开连接
var disconnectOptions = new MqttClientDisconnectOptions
{
Reason = (MqttClientDisconnectOptionsReason)MqttClientDisconnectReason.NormalDisconnection
};
await _mqttClient.DisconnectAsync(disconnectOptions, cancellationToken);
Console.WriteLine("\n【订阅者已断开连接】");
}
_mqttClient.Dispose();
}
}
}
第三步:发布者(Publisher)实现(发送消息)
cs
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTT示例
{
public class MqttPublisher
{
private IMqttClient _mqttClient;
/// <summary>
/// 初始化发布者(连接到Broker)
/// </summary>
public async Task InitAsync(CancellationToken cancellationToken = default)
{
var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient();
var options = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.BrokerAddress, MqttConfig.BrokerPort)
.WithClientId(MqttConfig.PublisherClientId)
.WithCredentials(MqttConfig.Username, MqttConfig.Password)
.WithCleanSession(true)
.Build();
// 连接状态事件
_mqttClient.DisconnectedAsync += e =>
{
Console.WriteLine($"\n【发布者断开连接】原因:{e.Reason}");
return Task.CompletedTask;
};
await _mqttClient.ConnectAsync(options, cancellationToken);
Console.WriteLine($"【发布者已连接】ClientId:{MqttConfig.PublisherClientId}");
}
/// <summary>
/// 发布消息
/// </summary>
/// <param name="messageContent">消息内容</param>
/// <param name="qos">QoS等级</param>
/// <param name="isRetained">是否为保留消息</param>
public async Task PublishMessageAsync(string messageContent,
MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtLeastOnce,
bool isRetained = false,
CancellationToken cancellationToken = default)
{
if (!_mqttClient.IsConnected)
{
throw new InvalidOperationException("发布者未连接到MQTT Broker");
}
// 构建消息
var message = new MqttApplicationMessageBuilder()
.WithTopic(MqttConfig.TestTopic) // 消息主题
.WithPayload(Encoding.UTF8.GetBytes(messageContent)) // 消息内容
.WithQualityOfServiceLevel(qos) // QoS等级
.WithRetainFlag(isRetained) // 是否保留消息
.Build();
// 发布消息
await _mqttClient.PublishAsync(message, cancellationToken);
Console.WriteLine($"\n【发布者发送消息】");
Console.WriteLine($"主题:{MqttConfig.TestTopic}");
Console.WriteLine($"内容:{messageContent}");
Console.WriteLine($"QoS:{qos}");
Console.WriteLine($"保留消息:{isRetained}");
}
/// <summary>
/// 停止发布者
/// </summary>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
if (_mqttClient.IsConnected)
{
var disconnectOptions = new MqttClientDisconnectOptions
{
Reason = (MqttClientDisconnectOptionsReason)MqttClientDisconnectReason.NormalDisconnection
};
await _mqttClient.DisconnectAsync(disconnectOptions, cancellationToken);
Console.WriteLine("\n【发布者已断开连接】");
}
_mqttClient.Dispose();
}
}
}
第四步:主程序(运行测试)
cs
namespace MQTT示例
{
internal class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== MQTT C# 演示程序 ===\n");
// 1. 启动订阅者(先启动,确保能接收后续消息)
var subscriber = new MqttSubscriber();
await subscriber.StartAsync();
// 2. 初始化发布者
var publisher = new MqttPublisher();
await publisher.InitAsync();
// 3. 循环发送消息(用户输入内容,按回车发送,输入exit退出)
Console.WriteLine("\n请输入要发送的消息(输入exit退出):");
string input;
while ((input = Console.ReadLine()) != "exit")
{
if (!string.IsNullOrWhiteSpace(input))
{
// 发布消息(QoS 1,非保留消息)
await publisher.PublishMessageAsync(input);
}
else
{
Console.WriteLine("消息内容不能为空!");
}
}
// 4. 清理资源
await publisher.StopAsync();
await subscriber.StopAsync();
Console.WriteLine("\n程序已退出,按任意键结束...");
Console.ReadKey();
}
}
}
3. 运行结果说明
- 运行程序后,首先看到订阅者连接成功并订阅主题;
- 发布者连接成功,提示输入消息;
- 输入任意内容(如 "Hello MQTT!"),按回车:
- 发布者控制台显示 "发送消息" 日志;
- 订阅者控制台立即显示 "收到消息" 日志,包含主题、内容、QoS 等信息;
- 输入 "exit",程序断开连接并退出。
4. 关键代码解释
|---------------------------------|--------------------------------------|
| 代码片段 | 作用 |
| MqttClientOptionsBuilder | 构建客户端连接配置(Broker 地址、客户端 ID、认证、遗嘱消息等) |
| ApplicationMessageReceivedAsync | 订阅者核心事件,收到消息时触发 |
| SubscribeAsync | 订阅指定主题,可设置订阅 QoS |
| MqttApplicationMessageBuilder | 构建发布的消息(主题、内容、QoS、保留消息等) |
| PublishAsync | 发布消息到 Broker |
| DisconnectAsync | 优雅断开连接(区别于异常断开,不会触发遗嘱消息) |
- 进阶使用建议
(1)断线重连
实际项目中需处理网络波动,推荐使用 ManagedMqttClient(MQTTnet 扩展),自动重连:
cs
// 替换普通客户端为托管客户端
var managedClient = new MqttFactory().CreateManagedMqttClient();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5)) // 重连间隔
.WithClientOptions(options)
.Build();
await managedClient.StartAsync(managedOptions);
(2)TLS/SSL 加密
若需安全通信,配置 TLS:
cs
var options = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.BrokerAddress, 8883) // SSL端口
.WithTlsOptions(o =>
{
o.WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12);
o.WithCertificateValidationHandler(_ => true); // 忽略证书验证(测试用,生产需验证)
})
.Build();
(3)批量订阅 / 发布
- 批量订阅:
WithTopicFilter可多次调用,订阅多个主题; - 批量发布:循环调用
PublishAsync,或使用PublishBatchAsync。
三、总结
- MQTT 核心:轻量级发布 / 订阅协议,核心角色为发布者、订阅者、Broker,通过主题路由消息;
- 关键特性:QoS(3 级可靠性)、保留消息、遗嘱消息,适配物联网低带宽场景;
- C# 实现 :
- 依赖
MQTTnet库,支持跨平台; - 订阅者需注册
ApplicationMessageReceivedAsync事件接收消息; - 发布者通过
PublishAsync发送消息,需指定主题、QoS 等参数; - 实际项目建议使用
ManagedMqttClient实现自动重连,提升稳定性。
- 依赖
- 客户端 ID 必须唯一,建议加随机数 / 设备 ID;
- 根据业务选择 QoS(非关键数据用 QoS 0,控制指令用 QoS 1,关键数据用 QoS 2);
- 生产环境需部署私有 Broker(如 EMQ X、Mosquitto),并配置认证、加密(TLS)。