应直接使用 MQTTnet 库,因其支持 .NET Standard 2.0+、TLS/SSL、异步、重连、QoS2 流控及生产验证;避免自行封装或选用老旧替代库。

直接用 MQTTnet 库,别自己封装 TCP + 协议解析——它已覆盖 .NET Standard 2.0+、支持 TLS/SSL、异步友好,且生产环境验证充分。
为什么选 MQTTnet 而不是其他库?
常见替代如 uPLibrary 或自行基于 TcpClient 实现,要么只支持旧框架(.NET Framework 4.x),要么缺少重连、会话恢复、QoS2 流控等关键能力。而 MQTTnet 的 MqttFactory 统一管理客户端生命周期,MqttClientOptions 显式控制 clean session、keep alive、遗嘱消息等行为,避免“连上了但收不到消息”这类黑盒问题。
实际踩坑点:
-
MqttClientOptions中ClientId必须唯一;重复连接时若设CleanSession = false,服务端会投递离线消息,但客户端必须用相同ClientId才能取到 - 默认不启用 TLS:连接
ssl://broker.hivemq.com:8883时,必须显式设置TlsOptions.UseTls = true,否则连接静默失败 - .NET 6+ 项目若引用了
Microsoft.Extensions.Logging,可直接注入日志器,否则需传入空实现,否则ConnectAsync可能卡住
如何正确连接并订阅主题(含错误处理)
不要在 ConnectAsync 后立刻调用 SubscribeAsync——两者非原子操作,网络抖动时可能订阅失败但连接成功,导致“看似连上却收不到消息”。应 await 连接完成,并检查 MqttClientConnectedArgs.SessionPresent 和返回码。
实操建议:
- 用
try/catch包裹ConnectAsync,捕获MqttCommunicationException(网络层)和MqttProtocolViolationException(协议参数错,如非法 ClientId) - 订阅前检查
IsConnected属性,避免在断线重连期间重复订阅造成主题冗余 - 使用
SubscribeAsync时传入MqttTopicFilterBuilder构造带 QoS 的过滤器,例如new MqttTopicFilterBuilder().WithTopic("sensor/temp").WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
发布消息时怎么避免丢数据?
PublishAsync 默认是 fire-and-forget,QoS0 下发即弃,网络中断或 broker 拒绝都不会抛异常。要确保送达,必须组合使用 QoS1+回调+本地确认逻辑。
关键配置与写法:
- 发布时指定
MqttApplicationMessageBuilder并设QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce - 监听
Client.ApplicationMessageReceivedAsync事件,仅当收到 broker 的 PUBACK 后才认为该消息被接收(注意:这个事件触发 ≠ 消息被消费,只是 broker 已存入) - 若业务要求“至少一次且去重”,需在应用层维护
MessageId缓存(如ConcurrentDictionary),防止重复投递 - 大消息(>128KB)可能被 broker 截断,建议先压缩再 Base64 编码,且设置
Retain = false避免占用服务端存储
断线重连与状态同步怎么做?
MQTTnet 不自动重连——这是有意设计:自动重连策略(指数退避?最大尝试次数?是否清空本地订阅?)必须由业务决定。强行开启“自动重连”反而掩盖连接不稳定的真实原因。
推荐做法:
- 订阅
Client.ConnectedAsync和Client.DisconnectedAsync事件,在断开时启动带退避的重连任务(如Task.Delay(2000 * (int)Math.Pow(2, retryCount))) - 重连成功后,不要盲目重新订阅全部主题;对比上次连接时的
SessionPresent == true,为 true 说明服务端仍保留会话,无需重复订阅 - 若使用
CleanSession = false,务必在首次连接时保存所有订阅的主题列表,断线重连后按需恢复,否则服务端不会推送历史消息
最易忽略的一点:MQTT 的“在线”只是 TCP 连接存在,不代表业务可用。真实场景中,需要配合心跳主题(如 client/xxx/status 发送 "online")、服务端超时下线机制,以及客户端本地定时检测 IsConnected && LastPingResponseTime > DateTime.Now.AddSeconds(-60) 才算真正存活。


















