一.IOT设备的特性

  • 硬件能力差(存储能力基本只有几MB,CPU频率低连使用HTTP请求都很奢侈)

  • 系统千差万别(Brillo,mbedOS,RIOT等)
  • 如使用电池供电,电量消耗敏感
  • 如果是小设备,设备基数大需要维持大量在线链接
  • 网络情况不稳定,移动网络网络资费贵,需要尽量减少开销和稳定

在以上这样苛刻的场景下很多技术上常用在智能设备方案都望而却步,总结一下我们主要面对下面三个问题:

  • socket.io,websocket? 不同的系统可能无法使用HTTP,设备资源可能使用HTTP都奢侈 。

  • TCP/IP自定协议? 虽然不用在意系统,自定义报文怎么解决网络开销问题?
  • 自主研发成本高,使用第三方IOT平台容易被技术或硬件绑定 。

二. MQTT为什么适合IOT场景

  • MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,使用方式比较类似于队列软件比如RabbitMQ,使用发布/订阅的方式提供互相之间的通讯。
  • MQTT是为在计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议。

MQTT主要特性:

  • 该协议支持所有平台,几乎可以把所有联网物品和外部连接起来

  • 有三种消息发布服务质量
    - “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    - “至少一次”,确保消息到达,但消息重复可能会发生。
    - “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
  • 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
  • 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制;

除了MQTT的协议特性外还有一些客观原因:

  • 对语言友好主流语言的客户端都有

  • 大部分硬件方案天生支持
  • 数十个MQTT服务器端程序可供选择
  • 社区成熟解决方案被广泛运用遇到问题方便寻求帮助

以上基本是我们为什么也会选择MQTT作为IOT协议的原因,需要更多的了解或者查看客户端支不支持和服务端实现可以参考官方github:

三、MQTTnet

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

MQTTnet 在 Github 还有 MqttDotNetnMQTTM2MQTT 等。

在解决方案在右键单击-选择“管理解决方案的 NuGet 程序包”-在“浏览”选项卡下面搜索 MQTTnet,为服务端项目和客户端项目都安装上 MQTTnet 库。

四、 服务端

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

1、 创建服务端并启动

IMqttServer  mqttServer = new MqttFactory().CreateMqttServer();

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

Task.Run(async () => { await mqttServer.StartAsync(optionsBuilder.Build()); });

2、 配置设置、验证客户端

WithDefaultEndpointPort是设置使用的端口,协议里默认是用1883,不过调试我改成8222了。
WithConnectionValidator是用于连接验证,验证client id,用户名,密码什么的。示例没用数据库,随便写死了两个值。

还有其他配置选项,比如加密协议,可以在官方文档里看看,示例就是先简单能用。

var optionsBuilder = new MqttServerOptionsBuilder()
.WithConnectionBacklog(100)
.WithDefaultEndpointPort(8222)
.WithConnectionValidator(ValidatingMqttClients())
;

private static Action<MqttConnectionValidatorContext> ValidatingMqttClients()
{
// Setup client validator.
var options =new MqttServerOptions();
options.ConnectionValidator = c =>
{
Dictionary<string, string> c_u = new Dictionary<string, string>();
c_u.Add("client001", "username001");
c_u.Add("client002", "username002");
Dictionary<string, string> u_psw = new Dictionary<string, string>();
u_psw.Add("username001", "psw001");
u_psw.Add("username002", "psw002"); if (c_u.ContainsKey(c.ClientId) && c_u[c.ClientId] == c.Username)
{
if (u_psw.ContainsKey(c.Username) && u_psw[c.Username] == c.Password)
{
c.ReturnCode = MqttConnectReturnCode.ConnectionAccepted;
}
else
{
c.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
}
}
else
{
c.ReturnCode = MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
}
};
return options.ConnectionValidator;
}

3、 相关事件

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

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

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

mqttServer.ApplicationMessageReceived += MqttServer_ApplicationMessageReceived;
mqttServer.ClientConnected += MqttServer_ClientConnected;
mqttServer.ClientDisconnected += MqttServer_ClientDisconnected;

五、 客户端

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

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

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

1、 创建客户端并连接

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

var factory = new MqttFactory();
var mqttClient = factory.CreateMqttClient();

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

await mqttClient.ConnectAsync(options, CancellationToken.None); // Since 3.0.5 with CancellationToken

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

// Create TCP based options using the builder.
var options = new MqttClientOptionsBuilder()
.WithClientId("Client1")
.WithTcpServer("broker.hivemq.com")
.WithCredentials("bud", "%spencer%")
.WithTls()
.WithCleanSession()
.Build();

2、 相关事件

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

mqttClient.UseApplicationMessageReceivedHandler(e =>
{
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Task.Run(() => mqttClient.PublishAsync("hello/world"));
});

3、 订阅消息

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

mqttClient.UseConnectedHandler(async e =>
{
// Subscribe to a topic
await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("家/客厅/空调/#").Build());
});

4、 发布消息

mqtt的消息包含topic和payload两部分。topic就是消息主题(类型),用于另外一端判断这个消息是干什么用的。payload就是实际想要发送的数据。

  • WithTopic给一个topic。
  • WithPayload给一个msg。
  • WithAtMostOnceQoS设置QoS,至多1次。也可以设为别的。
  • PublishAsync异步发送出去。

string topic = "topic/hello";
var message = new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(msg)
.WithAtMostOnceQoS()
.WithRetainFlag()
.Build();
await mqttServer.PublishAsync(message);

六、跟踪消息

// Write all trace messages to the console window.
MqttNetGlobalLogger.LogMessagePublished += (s, e) =>
{
var trace = $">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}";
if (e.TraceMessage.Exception != null)
{
trace += Environment.NewLine + e.TraceMessage.Exception.ToString();
} Console.WriteLine(trace);
};

七、 运行效果

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

1、 服务端


2、 客户端1


3、 客户端2


八、完整Demo

完整实例:https://github.com/landbroken/MQTTLearning

使用MQTTNet在WPF框架下搭建MQTT客户端: https://blog.csdn.net/lordwish/article/details/84970800

使用MQTTNet在WPF框架下创建MQTT服务端(broker):https://blog.csdn.net/lordwish/article/details/85042476

使用MQTTNet+ASP.NET Core创建MQTT服务器(broker):https://blog.csdn.net/lordwish/article/details/86708777

IOT设备通讯,MQTT物联网协议,MQTTnet的更多相关文章

  1. 小程序入门 MQTT物联网协议 publish 和订阅subscribe onenet 阿里云 百度云 基于GPRS模块(SIM800C/SIM900A/SIM868等)和STM32主控芯片

    本文基本公开了如何移植MQTT物联网协议到STM32平台上,并结合GPRS模块(SIM800C/SIM900A/SIM868等)实现publish和订阅topic从onenet,阿里云,百度云等.如果 ...

  2. MQTT物联网通讯协议入门

    目录 一.MQTT协议概念 发布/订阅机制 MQTT客户端 Broker代理(服务器) MQTT消息结构 二.MQTT协议实现原理 MQTT连接 MQTT消息发布 MQTT订阅机制 MQTT订阅确认 ...

  3. EMQ 与 mqtt 与 IOT设备

    1.IOT设备的特性 IOT(物联网things of internet)设备和传统的智能设备有什么区别,笔者总结下的IOT设备有如下特点: 硬件能力差(存储能力基本只有几MB,CPU频率低连使用HT ...

  4. 安天透过北美DDoS事件解读IoT设备安全——Mirai的主要感染对象是linux物联网设备,包括:路由器、网络摄像头、DVR设备,入侵主要通过telnet端口进行流行密码档暴力破解,或默认密码登陆,下载DDoS功能的bot,运行控制物联网设备

    安天透过北美DDoS事件解读IoT设备安全 安天安全研究与应急处理中心(安天CERT)在北京时间10月22日下午启动高等级分析流程,针对美国东海岸DNS服务商Dyn遭遇DDoS攻击事件进行了跟进分析. ...

  5. 【物联网】 9个顶级开发IoT项目的开源物联网平台(转)

    物联网(IoT)是帮助人工智能(AI)以更好的方式控制和理解事物的未来技术. 我们收集了一些最有名的物联网平台,帮助您以受控方式开发物联网项目. 物联网平台是帮助设置和管理互联网连接设备的组件套件. ...

  6. 9个顶级开发IoT项目的开源物联网平台

    https://blog.csdn.net/shnbiot/article/details/80432017 物联网(IoT)是帮助人工智能(AI)以更好的方式控制和理解事物的未来技术. 我们收集了一 ...

  7. 干货分享 | 3个开发IoT项目的开源物联网平台

    物联网(IoT)是帮助人工智能(AI)以更好的方式控制和理解事物的未来技术. 艾艺收集了一些最有名的物联网平台,帮助您以受控方式开发物联网项目.物联网平台是帮助设置和管理互联网连接设备的组件套件. 一 ...

  8. 物联网协议CoAP协议学习

    CoAP:Constrained Application Protocol协议是为物联网中资源受限的设备制定的应用层协议,即简化版的基于UDP的HTTP协议.其核心内容为资源抽象.REST式交互可扩展 ...

  9. OpenHarmony3.0如何轻松连接华为云IoT设备接入平台?

    摘要:本文主要介绍基于OpenHarmony 3.0版本来对接华为云IoT设备接入IoTDA,以小熊派BearPi-HM_Nano开发板为例,使用huaweicloud_iot_link SDK对接华 ...

随机推荐

  1. C++标准异常与自定义异常

    参见https://www.runoob.com/cplusplus/cpp-exceptions-handling.html C++ 标准的异常 C++ 提供了一系列标准的异常,定义在 中,我们可以 ...

  2. 【剑指offer】面试题 14. 剪绳子

    面试题 14. 剪绳子 LeetCode 题目描述 给你一根长度为 n 的绳子,请把绳子剪成 m 段(m.n 都是整数,n>1 并且 m>1),每段绳子的长度记为 k[0],k[1],·· ...

  3. Vue(六)插槽(2.6.0+)

    插槽在vue2.6.0开始有了新的更新 具名插槽(数据来自父组件) 子组件(定义插槽)这里版本前后没什么变化 <template> <div> <header> & ...

  4. excel文件导出和导入

    pom.xml添加依赖 @RestController @RequestMapping(value = "/excel") public class ExpImpExcelCont ...

  5. Hibernate-validator数据验证

    前言 数据效验工作在开发工作中,是非常重要的,保证数据的正确性,可靠性,安全性.不仅在前端进行效验,还要在后台继续进行效验. 前端做验证只是为了用户体验,比如控制按钮的显示隐藏,单页应用的路由跳转等等 ...

  6. vs中web api程序不包含适合于入口点的静态“Main”方法

    步骤:选择该项目的属性--应用程序--输出类型--类库

  7. FZU2018级算法第一次作业 1.1fibonacci (矩阵快速幂)

    题目 Winder最近在学习fibonacci 数列的相关知识.我们都知道fibonacci数列的递推公式是F(n)=F(n-1)+F(n-2)(n>=2 且n 为整数). Winder想知道的 ...

  8. REST framework之分页组件

    REST framework之分页组件 一 简单分页 查看第n页,每页显示n条 from rest_framework.pagination import PageNumberPagination # ...

  9. Navicat premium工具转储数据表的结构时,datatime字段报错

    Navicat premium工具导出数据库: Navicat premium工具导入数据库: 运行SQL文件,遇到的错误,红色下划线提示,发现:(SQL文件的时间有问题) 不是insert语句有问题 ...

  10. 解决COM组件在WPF设计器中命名空间不存在XXX的问题(附带如何在WPF中使用APlayer引擎)

    总结起来就是:设计器的版本要跟外部引用的库版本一致,否则XAML设计器就会显示不出来. 例如你的程序是X64的,但是引用的COM组件是32位的,就会显示不出来.这里的建议是:编译一个32位的COM中间 ...