.NET MQTT客户端反复连接断开问题及会话配置咨询
问题分析与解决方案
一、当前断连核心原因:同一ClientID重复连接
你的代码中同时创建了两个独立的IManagedMqttClient实例(managedMqttClientPublisher和managedMqttClientSubscriber),并且它们使用了同一个clientId。根据MQTT协议规则,Broker会拒绝同一ClientID的重复连接,后连接的客户端会直接踢掉先连接的实例,这就是你看到SessionTakenOver错误的直接原因——和之前的随机ClientID无关。
二、解决反复断连的紧急修复
MQTT客户端本身兼具发布和订阅能力,无需维护两个独立实例。修改代码,只保留一个客户端实例即可:
核心代码修改
- 合并客户端初始化逻辑,删除冗余的发布者实例
// 只保留一个全局客户端实例 private IManagedMqttClient mqttClient; private async void StartMqttClient() { applog.Debug($"In StartMqttClient()"); var mqttFactory = new MQTTnet.MqttFactory(); mqttClient = mqttFactory.CreateManagedMqttClient(); // 绑定事件 mqttClient.ConnectedAsync += OnConnected; mqttClient.DisconnectedAsync += OnDisconnected; mqttClient.ApplicationMessageReceivedAsync += OnMessageReceived; // 初始化连接选项 ManagedMqttClientOptions clientOptions; if (MqttUseTls) { clientOptions = WsSecureClientOptions(); } else { clientOptions = new ManagedMqttClientOptions { ClientOptions = WsInsecureOptions() }; } // 设置自动重连延迟 clientOptions.AutoReconnectDelay = TimeSpan.FromSeconds(10); await mqttClient.StartAsync(clientOptions); // 订阅主题 List<MqttTopicFilter> topicFilter = new List<MqttTopicFilter>(); topicFilter.Add(new MqttTopicFilter { Topic = topicThisHost }); topicFilter.Add(new MqttTopicFilter { Topic = topicSubFromHar }); await mqttClient.SubscribeAsync(topicFilter); // 发布初始消息 await Publish($"{defaultMessage} - Machine: {this.machineName}, Host: {this.SynergyHostName}", this.topicThisHost); }
- 修改
Publish方法,复用同一个客户端实例
public async Task Publish(string messageIn, string topic) { MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtLeastOnce; switch (MqttQos) { case 0: qos = MqttQualityOfServiceLevel.AtLeastOnce; break; case 1: qos = MqttQualityOfServiceLevel.AtMostOnce; break; case 2: qos = MqttQualityOfServiceLevel.ExactlyOnce; break; } MqttModel message = new MqttModel(); message.message = messageIn; message.datestamp = DateTime.Now; message.source = ""; message.status = ""; var payload = JsonConvert.SerializeObject(message, Formatting.Indented); var send = new MqttApplicationMessageBuilder() .WithTopic(topic) .WithPayload(payload) .WithQualityOfServiceLevel(qos) .WithRetainFlag(false) .Build(); if (this.mqttClient != null) { try { applog.Debug($"Mqtt Service Publish() method - about to pub mqtt message EnqueueAsync() - {messageIn} / {topic} "); await this.mqttClient.EnqueueAsync(send); MonitoringLogs logs = new MonitoringLogs(); logs.InsertIntoLog(message); } catch (Exception ex) { string errorMessage = $"Exception occured in Publish() method. {ex.Message}"; applog.Error(errorMessage); throw new Exception(errorMessage); } } else { applog.Info($"Mqtt Service Publish() method - mqttClient object appears to be NULL"); } }
- 更新启动逻辑
private void StartPublisherAndSubscriber() { StartMqttClient(); // 替换原来的StartSubscriber和StartPublisher CheckOtherServers(); }
三、清理旧ClientID会话
针对HiveMQ免费云版本:
- 未活跃的会话会在超过Broker设定的超时时间后自动清理(通常是客户端KeepAlive时间的1.5倍)
- 若需手动清理,可登录HiveMQ云控制台,在「Clients」页面找到所有旧的随机ClientID,手动删除对应会话
- 后续使用固定ClientID后,不会再产生大量无效会话,无需频繁清理
四、固定ClientID与CleanSession的选择
1. 是否使用固定ClientID?
是,这是最优方案:
- 固定ClientID可让Broker识别同一设备,便于跟踪设备状态、保留订阅关系和未送达的QoS1/2消息
- 避免产生大量无效会话,占用免费版的100设备配额
2. 是否设置CleanSession = false?
根据业务需求决定:
- 如果需要保留订阅关系(服务重启后无需重新订阅)、接收离线期间的QoS1/2消息,设置
CleanSession = false(对应MQTTv5的CleanStart = false) - 如果业务不需要离线消息,服务重启后重新订阅即可,设置
CleanSession = true(CleanStart = true)更轻量,不会占用Broker的会话存储资源
注意:你的代码中WsSecureClientOptions调用了.WithCleanSession(),等价于设置CleanStart = true(MQTTv5),若需持久化会话,需修改为:
.WithCleanSession(false) // MQTTv3.1.1语法 // 或MQTTv5明确设置 .WithCleanStart(false)
额外优化建议
- 完善断连事件的日志输出,便于排查问题:
private Task OnDisconnected(MqttClientDisconnectedEventArgs args) { applog.Error($"MQTT客户端断开连接,原因:{args.Reason},详情:{args.ReasonString}"); return Task.CompletedTask; }
- 确保
mqttClientPswd变量被正确初始化(当前代码仅声明未赋值,可能导致认证失败)
内容的提问来源于stack exchange,提问作者bob.mazzo
相关产品推荐
相关产品推荐

