You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MQTT客户端反复连接断开问题及会话配置咨询

问题分析与解决方案

一、当前断连核心原因:同一ClientID重复连接

你的代码中同时创建了两个独立的IManagedMqttClient实例(managedMqttClientPublisher和managedMqttClientSubscriber),并且它们使用了同一个clientId。根据MQTT协议规则,Broker会拒绝同一ClientID的重复连接,后连接的客户端会直接踢掉先连接的实例,这就是你看到SessionTakenOver错误的直接原因——和之前的随机ClientID无关。

二、解决反复断连的紧急修复

MQTT客户端本身兼具发布和订阅能力,无需维护两个独立实例。修改代码,只保留一个客户端实例即可:

核心代码修改

  1. 合并客户端初始化逻辑,删除冗余的发布者实例
// 只保留一个全局客户端实例
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);
}
  1. 修改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");
    }
}
  1. 更新启动逻辑
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:57:00