1 什么是 MQTT ?

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是 IBM 开发的一个即时通讯协议,有可能成为物联网的重要组成部分。MQTT 是基于二进制消息的发布/订阅编程模式的消息协议,如今已经成为 OASIS 规范,由于规范很简单,非常适合需要低功耗和网络带宽有限的 IoT 场景。MQTT官网

2 MQTTnet

MQTTnet 是一个基于 MQTT 通信的高性能 .NET 开源库,它同时支持 MQTT 服务器端和客户端。而且作者也保持更新,目前支持新版的.NET core,这也是选择 MQTTnet 的原因。 MQTTnet 在 Github 并不是下载最多的 .NET 的 MQTT 开源库,其他的还 MqttDotNetnMQTTM2MQTT

MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.

3 创建项目并导入类库

这里我们使用 Visual Studio 2017 创建一个空解决方案,并在其中添加两个项目,即一个服务端和一个客户端,服务端项目模板选择最新的 .NET Core 控制台应用,客户端项目选择传统的 WinForm 窗体应用程序。.NET Core 项目模板如下图所示:

在解决方案在右键单击-选择“管理解决方案的 NuGet 程序包”-在“浏览”选项卡下面搜索 MQTTnet,为服务端项目和客户端项目都安装上 MQTTnet 库,当前最新稳定版为 2.4.0。项目结构如下图所示:

4 服务端

MQTT 服务端主要用于与多个客户端保持连接,并处理客户端的发布和订阅等逻辑。一般很少直接从服务端发送消息给客户端(可以使用 mqttServer.Publish(appMsg); 直接发送消息),多数情况下服务端都是转发主题匹配的客户端消息,在系统中起到一个中介的作用。

4.1 创建服务端并启动

创建服务端最简单的方式是采用 MqttServerFactory 对象的 CreateMqttServer 方法来实现,该方法需要一个 MqttServerOptions 参数。

  1. var options = new MqttServerOptions();
  2. var mqttServer = new MqttServerFactory().CreateMqttServer(options);

通过上述方式创建了一个 IMqttServer 对象后,调用其 StartAsync 方法即可启动 MQTT 服务。值得注意的是:之前版本采用的是 Start 方法,作者也是紧跟 C# 语言新特性,能使用异步的地方也都改为异步方式。

  1. await mqttServer.StartAsync();

4.2 验证客户端

MqttServerOptions 选项中,你可以使用 ConnectionValidator 来对客户端连接进行验证。比如客户端ID标识 ClientId,用户名 Username 和密码 Password 等。

  1. var options = new MqttServerOptions
  2. {
  3. ConnectionValidator = c =>
  4. {
  5. if (c.ClientId.Length < 10)
  6. {
  7. return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
  8. }
  9. if (c.Username != "xxx" || c.Password != "xxx")
  10. {
  11. return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
  12. }
  13. return MqttConnectReturnCode.ConnectionAccepted;
  14. }
  15. };

4.3 相关事件

服务端支持 ClientConnectedClientDisconnectedApplicationMessageReceived 事件,分别用来检查客户端连接、客户端断开以及接收客户端发来的消息。

其中 ClientConnectedClientDisconnected 事件的事件参数一个客户端连接对象 ConnectedMqttClient,通过该对象可以获取客户端ID标识 ClientId 和 MQTT 版本 ProtocolVersion

ApplicationMessageReceived 的事件参数包含了客户端ID标识 ClientId 和 MQTT 应用消息 MqttApplicationMessage 对象,通过该对象可以获取主题 Topic、QoS QualityOfServiceLevel 和消息内容 Payload 等信息。

5 客户端

MQTT 与 HTTP 不同,后者是基于请求/响应方式的,服务器端无法直接发送数据给客户端。而 MQTT 是基于发布/订阅模式的,所有的客户端均与服务端保持连接状态。

那么客户端之间是如何通信的呢?

具体逻辑是:某些客户端向服务端订阅它感兴趣(主题)的消息,另一些客户端向服务端发布(主题)消息,服务端将订阅和发布的主题进行匹配,并将消息转发给匹配通过的客户端。

5.1 创建客户端并连接

使用 MQTTnet 创建 MQTT 也非常简单,只需要使用 MqttClientFactory 对象的 CreateMqttClient 方法即可。

  1. var mqttClient = new MqttClientFactory().CreateMqttClient();

创建客户端对象后,调用其异步方法 ConnectAsync 来连接到服务端。

  1. await mqttClient.ConnectAsync(options);

调用该方法时需要传递一个 MqttClientTcpOptions 对象(之前的版本是在创建对象时使用该选项),该选项包含了客户端ID标识 ClientId、服务端地址(可以使用IP地址或域名)Server、端口号 Port、用户名 UserName、密码 Password 等信息。

  1. var options = new MqttClientTcpOptions
  2. {
  3. Server = "127.0.0.1",
  4. ClientId = "c001",
  5. UserName = "u001",
  6. Password = "p001",
  7. CleanSession = true
  8. };

5.2 相关事件

客户端支持 ConnectedDisconnectedApplicationMessageReceived 事件,用来处理客户端与服务端连接、客户端从服务端断开以及客户端收到消息的事情。

5.2 订阅消息

客户端连接到服务端之后,可以使用 SubscribeAsync 异步方法订阅消息,该方法可以传入一个可枚举或可变参数的主题过滤器 TopicFilter 参数,主题过滤器包含主题名和 QoS 等级。

  1. mqttClient.SubscribeAsync(new List<TopicFilter> {
  2. new TopicFilter("家/客厅/空调/#", MqttQualityOfServiceLevel.AtMostOnce)
  3. });

5.3 发布消息

发布消息前需要先构建一个消息对象 MqttApplicationMessage,最直接的方法是使用其实构造函数,传入主题、内容、Qos 等参数。

  1. var appMsg = new MqttApplicationMessage("家/客厅/空调/开关", Encoding.UTF8.GetBytes("消息内容"), MqttQualityOfServiceLevel.AtMostOnce, false);

得到 MqttApplicationMessage 消息对象后,通过客户端对象调用其 PublishAsync 异步方法进行消息发布。

  1. mqttClient.PublishAsync(appMsg);

6 跟踪消息

MQTTnet 提供了一个静态类 MqttNetTrace 来对消息进行跟踪,该类可用于服务端和客户端。MqttNetTrace 的事件 TraceMessagePublished 用于跟踪服务端和客户端应用的日志消息,比如启动、停止、心跳、消息订阅和发布等。事件参数 MqttNetTraceMessagePublishedEventArgs 包含了线程ID ThreadId、来源 Source、日志级别 Level、日志消息 Message、异常信息 Exception 等。

  1. MqttNetTrace.TraceMessagePublished += MqttNetTrace_TraceMessagePublished;
  2. private static void MqttNetTrace_TraceMessagePublished(object sender, MqttNetTraceMessagePublishedEventArgs e)
  3. {
  4. Console.WriteLine($">> 线程ID:{e.ThreadId} 来源:{e.Source} 跟踪级别:{e.Level} 消息: {e.Message}");
  5. if (e.Exception != null)
  6. {
  7. Console.WriteLine(e.Exception);
  8. }
  9. }

同时 MqttNetTrace 类还提供了4个不同消息等级的静态方法,VerboseInformationWarningError,用于给出不同级别的日志消息,该消息将会在 TraceMessagePublished 事件中输出,你可以使用 e.Level 进行过虑。

7 运行效果

以下分别是服务端、客户端1和客户端2的运行效果,其中客户端1和客户端2只是同一个项目运行了两个实例。客户端1用于订阅传感器的“温度”数据,并模拟上位机(如 APP 等)发送开关控制命令;客户端2订阅上位机传来的“开关”控制命令,并模拟温度传感器上报温度数据。

7.1 服务端

7.2 客户端1

7.2 客户端2

8 Demo代码

8.1 服务端代码

  1. using MQTTnet;
  2. using MQTTnet.Core.Adapter;
  3. using MQTTnet.Core.Diagnostics;
  4. using MQTTnet.Core.Protocol;
  5. using MQTTnet.Core.Server;
  6. using System;
  7. using System.Text;
  8. using System.Threading;
  9. namespace MqttServerTest
  10. {
  11. class Program
  12. {
  13. private static MqttServer mqttServer = null;
  14. static void Main(string[] args)
  15. {
  16. MqttNetTrace.TraceMessagePublished += MqttNetTrace_TraceMessagePublished;
  17. new Thread(StartMqttServer).Start();
  18. while (true)
  19. {
  20. var inputString = Console.ReadLine().ToLower().Trim();
  21. if (inputString == "exit")
  22. {
  23. mqttServer?.StopAsync();
  24. Console.WriteLine("MQTT服务已停止!");
  25. break;
  26. }
  27. else if (inputString == "clients")
  28. {
  29. foreach (var item in mqttServer.GetConnectedClients())
  30. {
  31. Console.WriteLine($"客户端标识:{item.ClientId},协议版本:{item.ProtocolVersion}");
  32. }
  33. }
  34. else
  35. {
  36. Console.WriteLine($"命令[{inputString}]无效!");
  37. }
  38. }
  39. }
  40. private static void StartMqttServer()
  41. {
  42. if (mqttServer == null)
  43. {
  44. try
  45. {
  46. var options = new MqttServerOptions
  47. {
  48. ConnectionValidator = p =>
  49. {
  50. if (p.ClientId == "c001")
  51. {
  52. if (p.Username != "u001" || p.Password != "p001")
  53. {
  54. return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
  55. }
  56. }
  57. return MqttConnectReturnCode.ConnectionAccepted;
  58. }
  59. };
  60. mqttServer = new MqttServerFactory().CreateMqttServer(options) as MqttServer;
  61. mqttServer.ApplicationMessageReceived += MqttServer_ApplicationMessageReceived;
  62. mqttServer.ClientConnected += MqttServer_ClientConnected;
  63. mqttServer.ClientDisconnected += MqttServer_ClientDisconnected;
  64. }
  65. catch (Exception ex)
  66. {
  67. Console.WriteLine(ex.Message);
  68. return;
  69. }
  70. }
  71. mqttServer.StartAsync();
  72. Console.WriteLine("MQTT服务启动成功!");
  73. }
  74. private static void MqttServer_ClientConnected(object sender, MqttClientConnectedEventArgs e)
  75. {
  76. Console.WriteLine($"客户端[{e.Client.ClientId}]已连接,协议版本:{e.Client.ProtocolVersion}");
  77. }
  78. private static void MqttServer_ClientDisconnected(object sender, MqttClientDisconnectedEventArgs e)
  79. {
  80. Console.WriteLine($"客户端[{e.Client.ClientId}]已断开连接!");
  81. }
  82. private static void MqttServer_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
  83. {
  84. Console.WriteLine($"客户端[{e.ClientId}]>> 主题:{e.ApplicationMessage.Topic} 负荷:{Encoding.UTF8.GetString(e.ApplicationMessage.Payload)} Qos:{e.ApplicationMessage.QualityOfServiceLevel} 保留:{e.ApplicationMessage.Retain}");
  85. }
  86. private static void MqttNetTrace_TraceMessagePublished(object sender, MqttNetTraceMessagePublishedEventArgs e)
  87. {
  88. /*Console.WriteLine($">> 线程ID:{e.ThreadId} 来源:{e.Source} 跟踪级别:{e.Level} 消息: {e.Message}");
  89. if (e.Exception != null)
  90. {
  91. Console.WriteLine(e.Exception);
  92. }*/
  93. }
  94. }
  95. }

8.2 客户端代码

  1. using MQTTnet;
  2. using MQTTnet.Core;
  3. using MQTTnet.Core.Client;
  4. using MQTTnet.Core.Packets;
  5. using MQTTnet.Core.Protocol;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using System.Windows.Forms;
  11. namespace MqttClientWin
  12. {
  13. public partial class FmMqttClient : Form
  14. {
  15. private MqttClient mqttClient = null;
  16. public FmMqttClient()
  17. {
  18. InitializeComponent();
  19. Task.Run(async () => { await ConnectMqttServerAsync(); });
  20. }
  21. private async Task ConnectMqttServerAsync()
  22. {
  23. if (mqttClient == null)
  24. {
  25. mqttClient = new MqttClientFactory().CreateMqttClient() as MqttClient;
  26. mqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;
  27. mqttClient.Connected += MqttClient_Connected;
  28. mqttClient.Disconnected += MqttClient_Disconnected;
  29. }
  30. try
  31. {
  32. var options = new MqttClientTcpOptions
  33. {
  34. Server = "127.0.0.1",
  35. ClientId = Guid.NewGuid().ToString().Substring(0, 5),
  36. UserName = "u001",
  37. Password = "p001",
  38. CleanSession = true
  39. };
  40. await mqttClient.ConnectAsync(options);
  41. }
  42. catch (Exception ex)
  43. {
  44. Invoke((new Action(() =>
  45. {
  46. txtReceiveMessage.AppendText($"连接到MQTT服务器失败!" + Environment.NewLine + ex.Message + Environment.NewLine);
  47. })));
  48. }
  49. }
  50. private void MqttClient_Connected(object sender, EventArgs e)
  51. {
  52. Invoke((new Action(() =>
  53. {
  54. txtReceiveMessage.AppendText("已连接到MQTT服务器!" + Environment.NewLine);
  55. })));
  56. }
  57. private void MqttClient_Disconnected(object sender, EventArgs e)
  58. {
  59. Invoke((new Action(() =>
  60. {
  61. txtReceiveMessage.AppendText("已断开MQTT连接!" + Environment.NewLine);
  62. })));
  63. }
  64. private void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
  65. {
  66. Invoke((new Action(() =>
  67. {
  68. txtReceiveMessage.AppendText($">> {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}{Environment.NewLine}");
  69. })));
  70. }
  71. private void BtnSubscribe_ClickAsync(object sender, EventArgs e)
  72. {
  73. string topic = txtSubTopic.Text.Trim();
  74. if (string.IsNullOrEmpty(topic))
  75. {
  76. MessageBox.Show("订阅主题不能为空!");
  77. return;
  78. }
  79. if (!mqttClient.IsConnected)
  80. {
  81. MessageBox.Show("MQTT客户端尚未连接!");
  82. return;
  83. }
  84. mqttClient.SubscribeAsync(new List<TopicFilter> {
  85. new TopicFilter(topic, MqttQualityOfServiceLevel.AtMostOnce)
  86. });
  87. txtReceiveMessage.AppendText($"已订阅[{topic}]主题" + Environment.NewLine);
  88. txtSubTopic.Enabled = false;
  89. btnSubscribe.Enabled = false;
  90. }
  91. private void BtnPublish_Click(object sender, EventArgs e)
  92. {
  93. string topic = txtPubTopic.Text.Trim();
  94. if (string.IsNullOrEmpty(topic))
  95. {
  96. MessageBox.Show("发布主题不能为空!");
  97. return;
  98. }
  99. string inputString = txtSendMessage.Text.Trim();
  100. var appMsg = new MqttApplicationMessage(topic, Encoding.UTF8.GetBytes(inputString), MqttQualityOfServiceLevel.AtMostOnce, false);
  101. mqttClient.PublishAsync(appMsg);
  102. }
  103. }
  104. }

9 参考

使用 MQTTnet 快速实现 MQTT 通信的更多相关文章

  1. MQTT(一)C#使用 MQTTnet 快速实现 MQTT 通信(文末有完整Demo下载)

    https://blog.csdn.net/panwen1111/article/details/79245161 目录MQTT(一)C#使用 MQTTnet 快速实现 MQTT 通信(文末有完整De ...

  2. MQTT实战2 - 使用MQTTnet实现mqtt通信

    MQTT实战1 - 使用Apache Apollo代理服务器实现mqtt通信 MQTT实战2 - 使用MQTTnet实现mqtt通信 源码下载 -> 提取码  QQ:505645074 MQTT ...

  3. MQTT实战1 - 使用Apache Apollo代理服务器实现mqtt通信

    MQTT实战1 - 使用Apache Apollo代理服务器实现mqtt通信 MQTT实战2 - 使用MQTTnet实现mqtt通信 源码下载 -> 提取码  QQ:505645074 MQTT ...

  4. 快速开发MQTT(一)电子工程师眼中的MQTT

    转载:https://zhuanlan.zhihu.com/p/54669124 DigCore 主页http://www.digcore.cn 文章首发于同名微信公众号:DigCore 欢迎关注同名 ...

  5. MQTT 协议学习:001-搭建MQTT通信环境,并抓包测试

    背景 目的:了解MQTT 通信的有关概念与流程:方便推算某些数据与文档描述是否一致. 为了能够在保证学习质量的前提下,降低配置环境的门槛,我们将服务器搭建在windwos中,实行内网间的MQTT协议访 ...

  6. 【.NET+MQTT】.NET6 环境下实现MQTT通信,以及服务端、客户端的双边消息订阅与发布的代码演示

    前言: MQTT广泛应用于工业物联网.智能家居.各类智能制造或各类自动化场景等.MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,在很多受限的环境下,比如说机器与机器通信.机器与物联网通信等. ...

  7. WiFi-ESP8266入门http(3-4)网页一键配网(1若为普通wifi直连 2若为西电网页认证自动网页post请求连接)+网页按钮灯控+MQTT通信

    网页一键配网(1若为普通wifi直连  2若为西电网页认证自动网页post请求连接)+网页按钮灯控+MQTT通信 工程连接:https://github.com/Dongvdong/ESP8266_H ...

  8. 快速搭建MQTT服务器(MQTTnet和Apache Apollo)

    前言 MQTT协议是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分,http://mqtt.org/. MQTT is a machine-to-machine (M2M)/" ...

  9. ESA2GJK1DH1K基础篇: 关于各大物联网平台的MQTT通信

    前言 这节稍微唠叨点 其实我很长时间都没有出怎么连接现成的物联网平台的教程, 一直写的是教给大家自己搭建服务器,主要原因是因为我感觉连接现有的学不到东西. 现在出这种教程,是因为发现确实很多人喜欢用. ...

随机推荐

  1. P3703 [SDOI2017]树点涂色

    P3703 [SDOI2017]树点涂色 链接 分析: 首先对于询问,感觉是线段树维护dfs序,每个点记录到根的颜色个数.第二问差分,第三问区间取max. 那么考虑修改,每次将一个点的颜色变成和父节点 ...

  2. CF 1110 D. Jongmah

    D. Jongmah 链接 题意: 一些数字,有两种方式组成一个三元组,[x,x,x],[x,x+1,x+2],每个数字只能用一次,求最多组成多少三元组. 分析: 因为每三个[x,x+1,x+2]是可 ...

  3. [洛谷P2057][bzoj1934]善意的投票(最大流)

    题目描述 幼儿园里有n个小朋友打算通过投票来决定睡不睡午觉.对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神.虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以投和自己本来 ...

  4. target属性用于返回最初触发事件的DOM元素。

    target属性用于返回最初触发事件的DOM元素. 在HTML文档中,我们为<p>元素绑定点击事件("click"),由于DOM元素的事件冒泡机制,我们点击<p& ...

  5. linux 查看服务器序列号

    1.安装工具 dmidecode yum install dmidecode -y 2.使用 dmidecode -t  1

  6. 沧桑巨变中焕发青春活力-记极1s HC5661A 打怪升级之路

    最近发现一个新货umaxhosting年付10美元的便宜VPS.2杯喜茶的价格可以让你在国外拥有一个1024MB (1GB) DDR3 RAM.1024MB (1GB) vSwap.70GB RAID ...

  7. Appium+python的单元测试框架unittest(1)(转)

    unittest为python语言自带的单元测试框架,python把unittest封装为一个标准模块封装在python开发包中.unittest中常用的类有:unittest.TestCase.un ...

  8. monkey测试入门1

    Monkey是一款通过命令行来对我们APP进行测试的工具,可以运行在模拟器里或真机上.它向系统发送伪随机的用户事件流,实现对正应用程序进行压力测试. 官方介绍 :https://developer.a ...

  9. c语言数字图像处理(六):二维离散傅里叶变换

    基础知识 复数表示 C = R + jI 极坐标:C = |C|(cosθ + jsinθ) 欧拉公式:C = |C|ejθ 有关更多的时域与复频域的知识可以学习复变函数与积分变换,本篇文章只给出DF ...

  10. TPO-19 C2 Cafeteria's Food Policy

    TPO-19 C2 Cafeteria's Food Policy 第 1 段 1.Listen to a conversation between a student and the directo ...