MQTT 概念详解与 C# 实战

一、MQTT 核心概念

1. 什么是 MQTT?

MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布 / 订阅模式的物联网通信协议,由 IBM 开发,专为低带宽、高延迟、不可靠网络设计(如物联网设备、传感器、移动网络)。

核心特点:

  • 轻量级:协议头最小仅 2 字节,占用资源极少
  • 发布 / 订阅模式:解耦消息发送者(发布者)和接收者(订阅者)
  • QoS 消息质量:支持 3 级消息可靠性保障
  • 持久会话:客户端断开后可恢复会话,避免消息丢失
  • 遗嘱消息:客户端异常断开时,代理自动发送预设消息

2. MQTT 核心角色

|-----------------|-------------------------------|
| 角色 | 说明 |
| 发布者(Publisher) | 发送消息的客户端(如传感器、APP),无需知道谁接收消息 |
| 订阅者(Subscriber) | 接收消息的客户端(如服务器、APP),只需订阅感兴趣的主题 |
| 代理(Broker) | 消息中间件(服务器),负责接收发布者消息并转发给订阅者 |

3. 核心术语

(1)主题(Topic)

消息的 "地址",订阅者通过主题筛选接收的消息,格式类似文件路径:

  • 示例:sensor/temperature/room1device/light/control
  • 通配符:
    • +:匹配单层主题(如 sensor/+/room1 匹配 sensor/temperature/room1
    • #:匹配多层主题(如 sensor/# 匹配 sensor/temperature/room1sensor/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. 运行结果说明

  1. 运行程序后,首先看到订阅者连接成功并订阅主题;
  2. 发布者连接成功,提示输入消息;
  3. 输入任意内容(如 "Hello MQTT!"),按回车:
    • 发布者控制台显示 "发送消息" 日志;
    • 订阅者控制台立即显示 "收到消息" 日志,包含主题、内容、QoS 等信息;
  4. 输入 "exit",程序断开连接并退出。

4. 关键代码解释

|---------------------------------|--------------------------------------|
| 代码片段 | 作用 |
| MqttClientOptionsBuilder | 构建客户端连接配置(Broker 地址、客户端 ID、认证、遗嘱消息等) |
| ApplicationMessageReceivedAsync | 订阅者核心事件,收到消息时触发 |
| SubscribeAsync | 订阅指定主题,可设置订阅 QoS |
| MqttApplicationMessageBuilder | 构建发布的消息(主题、内容、QoS、保留消息等) |
| PublishAsync | 发布消息到 Broker |
| DisconnectAsync | 优雅断开连接(区别于异常断开,不会触发遗嘱消息) |

  1. 进阶使用建议
(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

三、总结

  1. MQTT 核心:轻量级发布 / 订阅协议,核心角色为发布者、订阅者、Broker,通过主题路由消息;
  2. 关键特性:QoS(3 级可靠性)、保留消息、遗嘱消息,适配物联网低带宽场景;
  3. C# 实现
    • 依赖 MQTTnet 库,支持跨平台;
    • 订阅者需注册 ApplicationMessageReceivedAsync 事件接收消息;
    • 发布者通过 PublishAsync 发送消息,需指定主题、QoS 等参数;
    • 实际项目建议使用 ManagedMqttClient 实现自动重连,提升稳定性。
  • 客户端 ID 必须唯一,建议加随机数 / 设备 ID;
  • 根据业务选择 QoS(非关键数据用 QoS 0,控制指令用 QoS 1,关键数据用 QoS 2);
  • 生产环境需部署私有 Broker(如 EMQ X、Mosquitto),并配置认证、加密(TLS)。
相关推荐
代码改善世界2 小时前
栈和队列的实现与详解(C语言版):从底层原理到代码实战
c语言·开发语言
无名之逆3 小时前
你可能不需要WebSocket-服务器发送事件的简单力量
java·开发语言·前端·后端·计算机·rust·编程
Remember_9933 小时前
一文吃透Java WebSocket:原理、实现与核心特性解析
java·开发语言·网络·websocket·网络协议·http·p2p
锅包一切3 小时前
一、C++ 发展与程序创建
开发语言·c++·后端·学习·编程
一株菌子3 小时前
10.12 总结
开发语言·python
枷锁—sha3 小时前
【CTFshow-pwn系列】03_栈溢出【pwn 051】详解:C++字符串替换引发的血案与 Ret2Text
开发语言·网络·c++·笔记·安全·网络安全
沙白猿4 小时前
【TJXT】Day3
java·开发语言
一个处女座的程序猿O(∩_∩)O4 小时前
Python面向对象的封装特性详解
开发语言·python
zhaoyin19944 小时前
python基础
开发语言·python