使用 MQTTnet 快速实现 MQTT 通信

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 参数。

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

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

await mqttServer.StartAsync();

4.2 验证客户端

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

var options = new MqttServerOptions{   
 ConnectionValidator = c =>{        
 if (c.ClientId.Length < 10){      
      return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;}      
       if (c.Username != "xxx" || c.Password != "xxx"){          
        return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;}        
        return MqttConnectReturnCode.ConnectionAccepted;} };

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 方法即可。

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

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

await mqttClient.ConnectAsync(options);

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

var options = new MqttClientTcpOptions
{Server = "127.0.0.1",ClientId = "c001",UserName = "u001",Password = "p001",CleanSession = true};

5.2 相关事件

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

5.2 订阅消息

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

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

5.3 发布消息

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

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

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

mqttClient.PublishAsync(appMsg);

6 跟踪消息

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

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

同时 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 服务端代码

using MQTTnet;
using
MQTTnet.Core.Adapter;
using
MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Protocol;
using
MQTTnet.Core.Server;
using System;using System.Text;
using System.Threading;
namespace
MqttServerTest{  
 class Program{        
 private static MqttServer mqttServer = null;    
    static void Main(string[] args)        {MqttNetTrace.TraceMessagePublished += MqttNetTrace_TraceMessagePublished;          
      new Thread(StartMqttServer).Start();        
         while (true){              
              var inputString = Console.ReadLine().ToLower().Trim();             if (inputString == "exit"){mqttServer?.StopAsync();Console.WriteLine("MQTT服务已停止!");                    break;}              
               else if (inputString == "clients")      
         
{                  
                 foreach (var item in mqttServer.GetConnectedClients()){Console.WriteLine($"客户端标识:{item.ClientId},协议版本:{item.ProtocolVersion}");}}          
               else{Console.WriteLine($"命令[{inputString}]无效!");}}}    
        private static void StartMqttServer()        {            if (mqttServer == null){          
             try{                  
               var options = new MqttServerOptions{ConnectionValidator = p =>{                          
                 if (p.ClientId == "c001"){                      
                          if (p.Username != "u001" || p.Password != "p001"){                                    return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;}}                  
                           return MqttConnectReturnCode.ConnectionAccepted;}};mqttServer = new MqttServerFactory().CreateMqttServer(options) as MqttServer;mqttServer.ApplicationMessageReceived += MqttServer_ApplicationMessageReceived;mqttServer.ClientConnected += MqttServer_ClientConnected;mqttServer.ClientDisconnected += MqttServer_ClientDisconnected;}                catch (Exception ex){Console.WriteLine(ex.Message);                    return;}}mqttServer.StartAsync();Console.WriteLine("MQTT服务启动成功!");}    
       private static void MqttServer_ClientConnected(object sender, MqttClientConnectedEventArgs e)        {Console.WriteLine($"客户端[{e.Client.ClientId}]已连接,协议版本:{e.Client.ProtocolVersion}");}
       
       private static void MqttServer_ClientDisconnected(object sender, MqttClientDisconnectedEventArgs e)        {Console.WriteLine($"客户端[{e.Client.ClientId}]已断开连接!");}  
       
       private static void MqttServer_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)        {Console.WriteLine($"客户端[{e.ClientId}]>> 主题:{e.ApplicationMessage.Topic} 负荷:{Encoding.UTF8.GetString(e.ApplicationMessage.Payload)} Qos:{e.ApplicationMessage.QualityOfServiceLevel} 保留:{e.ApplicationMessage.Retain}");}  
       
       private static void MqttNetTrace_TraceMessagePublished(object sender, MqttNetTraceMessagePublishedEventArgs e)        {            /*Console.WriteLine($">> 线程ID:{e.ThreadId} 来源:{e.Source} 跟踪级别:{e.Level} 消息: {e.Message}");if (e.Exception != null){Console.WriteLine(e.Exception);}*/}} }

8.2 客户端代码

using MQTTnet;
using MQTTnet.Core;
using
MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using System;
using System.Collections.Generic;
using
System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MqttClientWin{  
 public partial class FmMqttClient : Form{      
    private MqttClient mqttClient = null;    
    public FmMqttClient()        {InitializeComponent();Task.Run(async () => { await ConnectMqttServerAsync(); });}      
  
    private async Task ConnectMqttServerAsync()        {            if (mqttClient == null){mqttClient = new MqttClientFactory().CreateMqttClient() as MqttClient;mqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;mqttClient.Connected += MqttClient_Connected;mqttClient.Disconnected += MqttClient_Disconnected;}          
      try{            
         var options = new MqttClientTcpOptions{Server = "127.0.0.1",ClientId = Guid.NewGuid().ToString().Substring(0, 5),UserName = "u001",Password = "p001",CleanSession = true};          
               await mqttClient.ConnectAsync(options);}          
            catch (Exception ex){Invoke((new Action(() =>{txtReceiveMessage.AppendText($"连接到MQTT服务器失败!" + Environment.NewLine + ex.Message + Environment.NewLine);})));}}      
       
        private void MqttClient_Connected(object sender, EventArgs e)        {Invoke((new Action(() =>{txtReceiveMessage.AppendText("已连接到MQTT服务器!" + Environment.NewLine);})));}  
        
         private void MqttClient_Disconnected(object sender, EventArgs e)        {Invoke((new Action(() =>{txtReceiveMessage.AppendText("已断开MQTT连接!" + Environment.NewLine);})));}        private void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)        {Invoke((new Action(() =>{txtReceiveMessage.AppendText($">> {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}{Environment.NewLine}");})));}
         private void BtnSubscribe_ClickAsync(object sender, EventArgs e)        {            string topic = txtSubTopic.Text.Trim();            if (string.IsNullOrEmpty(topic)){MessageBox.Show("订阅主题不能为空!");                return;}            if (!mqttClient.IsConnected){MessageBox.Show("MQTT客户端尚未连接!");                return;}mqttClient.SubscribeAsync(new List<TopicFilter> {                new TopicFilter(topic, MqttQualityOfServiceLevel.AtMostOnce)});txtReceiveMessage.AppendText($"已订阅[{topic}]主题" + Environment.NewLine);txtSubTopic.Enabled = false;btnSubscribe.Enabled = false;}        private void BtnPublish_Click(object sender, EventArgs e)        {            string topic = txtPubTopic.Text.Trim();            if (string.IsNullOrEmpty(topic)){MessageBox.Show("发布主题不能为空!");                return;}            string inputString = txtSendMessage.Text.Trim();            var appMsg = new MqttApplicationMessage(topic, Encoding.UTF8.GetBytes(inputString), MqttQualityOfServiceLevel.AtMostOnce, false);mqttClient.PublishAsync(appMsg);}} }

9 参考

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

  • MQTTnet

  • “mqtt” - 可译网

  • MQTT Essentials


原文:http://www.cnblogs.com/kuige/articles/7724786.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322679.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git代码合并冲突与撤回提交

查看版本 切回到某一个版本 git log --graph --abbrev-commit --decorate --prettyoneline git reset --hard a07cefe git有一种情况会造成代码被冲掉&#xff1a; 这里有A端和B端&#xff1a; 相同文本基础之上 A端写了大量代码&#xff0c;提交推送 B端拉取&#xff0c;改了代…

Asp.net Core中SignalR Core预览版的一些新特性前瞻,附源码(消息订阅与发送二进制数据)

前言 一晃一个月又过去了,上个月有个比较大的项目要验收上线.所以忙的脚不沾地.现在终于可以忙里偷闲,写一篇关于SignalR Core的文章了. 先介绍一下SignalR吧,如下: ASP.NET SignalR是ASP.NET开发人员的一个库&#xff0c;它简化了向Web应用程序添加即时通讯功能的过程。 它可以…

大二暑假工作三个月后辞职,总体感悟

本人是个大二的学生&#xff0c;因为疫情影响&#xff0c;学校放了很长时间的假。当时对自己的前端技术很自信&#xff0c;大概在五月底的时候决定去上海闯一下&#xff0c;找个工作&#xff0c;不管能不能找到&#xff0c;就是尝试一下&#xff0c;失败了也没关系&#xff0c;…

35年编程史沉淀下来的8条宝贵经

01 1. 时刻提醒自己&#xff1a;学习 学习某件事的第一步是承认你不知道。这听起来很正常&#xff0c;但经验丰富的程序员还记得要真正让自己承认这一点需要花多长时间。很多计算机科学专业的学生毕业的时候&#xff0c;都有一种很傲慢的态度&#xff0c;就是“我知道最好的”&…

基于Emgu CV+百度人脸识别,实现视频动态 人脸抓取与识别

背景 目前AI 处于风口浪尖&#xff0c;作为 公司的CTO&#xff0c;也作为自己的技术专研&#xff0c;开始了AI之旅&#xff0c;在朋友圈中也咨询 一些大牛对于AI 机器学习框架的看法&#xff0c;目前自己的研究方向主要开源的 AI 库&#xff0c;如&#xff1a;Emgu CV、TensorF…

vue-cli2、vue-cli3脚手架详细讲解

转载自 vue-cli2、vue-cli3脚手架详细讲解 前言&#xff1a; vue脚手架指的是vue-cli它是vue官方提供的一个快速构建单页面&#xff08;SPA&#xff09;环境配置的工具&#xff0c;cli 就是(command-line-interface ) 命令行界面 。vue-cli是基于node环境利用webpack对文件进…

微软为.NET程序员带来了最优的跨平台开发体验-WSL

前言 在前几个Visual Studio Code更新中发现有一个重要得特性&#xff0c;就是nodejs可以使用VS Code在WSL中进行Debug了&#xff08;WSL是指Win10中的Linux子系统&#xff09;,之前写过一篇文章是使用SSH对Linux环境进行Debug&#xff0c;此时的想法就是如果可以在WSL中直接对…

article之api文档

查 method:get http://127.0.0.1:8000/article 单条 http://127.0.0.1:8000/article/10 method:get 新增 http://127.0.0.1:8000/article method:post 修改 http://127.0.0.1:8000/article/10 method:put 删除 http://127.0.0.1:8000/article/3 method:delete ## 查询所有数据&…

Redis 性能问题分析

转载自 Redis 性能问题分析 在一些网络服务的系统中&#xff0c;Redis 的性能&#xff0c;可能是比 MySQL 等硬盘数据库的性能更重要的课题。比如微博&#xff0c;把热点微博[1]&#xff0c;最新的用户关系&#xff0c;都存储在 Redis 中&#xff0c;大量的查询击中 Redis&am…

谈谈微服务中的 API 网关(API Gateway)

前言 又是很久没写博客了&#xff0c;最近一段时间换了新工作&#xff0c;比较忙&#xff0c;所以没有抽出来太多的时间写给关注我的粉丝写一些干货了&#xff0c;就有人问我怎么最近没有更新博客了&#xff0c;在这里给大家抱歉。 那么&#xff0c;在本篇文章中&#xff0c;我…

laravel如何生成swagger接口文档

php artisan serve --host 0.0.0.0 php artisan serve --port 8080 地址&#xff1a; http://127.0.0.1/blogkjh/public/api/documentation 1、安装包 composer require darkaonline/l5-swagger 2、配置 php artisan vendor:publish --provider “L5Swagger\L5SwaggerService…

OAuth2 实现单点登录 SSO

转载自 OAuth2 实现单点登录 SSO 1. 前言 技术这东西吧&#xff0c;看别人写的好像很简单似的&#xff0c;到自己去写的时候就各种问题&#xff0c;“一看就会&#xff0c;一做就错”。网上关于实现SSO的文章一大堆&#xff0c;但是当你真的照着写的时候就会发现根本不是那么…

Ocelot网关

Ocelot是一个.net core框架下的网关的开源项目&#xff0c;下图是官方给出的基础实现图&#xff0c;即把后台的多个服务统一到网关处&#xff0c;前端应用&#xff1a;桌面端&#xff0c;web端&#xff0c;app端都只用访问网关即可。 Ocelot的实现原理就是把客户端对网关的请求…

百度OCR文字识别-身份证识别

简介 答应了园区大牛张善友 要写AI 的系列博客&#xff0c;所以开始了AI 系列之旅。 一、介绍 身份证识别 API 接口文档地址&#xff1a;http://ai.baidu.com/docs#/OCR-API/top 接口描述 用户向服务请求识别身份证&#xff0c;身份证识别包括正面和背面。 请求说明 请求示例…

Spring Boot Elasticsearch 入门

转载自 芋道 Spring Boot Elasticsearch 入门 1. 概述 如果胖友之前有用过 Elasticsearch 的话&#xff0c;可能有过被使用的 Elasticsearch 客户端版本搞死搞活。如果有&#xff0c;那么一起握个抓。所以&#xff0c;我们在文章的开始&#xff0c;先一起理一理这块。 Elas…

在.NET Core类库中使用EF Core迁移数据库到SQL Server

前言 如果大家刚使用EntityFramework Core作为ORM框架的话&#xff0c;想必都会遇到数据库迁移的一些问题。 起初我是在ASP.NET Core的Web项目中进行的&#xff0c;但后来发现放在此处并不是很合理&#xff0c;一些关于数据库的迁移&#xff0c;比如新增表&#xff0c;字段&…

Spring Boot MongoDB 入门

转载自 芋道 Spring Boot MongoDB 入门 1. 概述 可能有一些胖友对 MongoDB 不是很了解&#xff0c;这里我们引用一段介绍&#xff1a; FROM 《分布式文档存储数据库 MongoDB》 MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最…

Spring框架-事务管理注意事项

转载自 Spring框架-事务管理注意事项 常见事务问题 事务不起作用 可能是配置不起效&#xff0c;如扫描问题 事务自动提交了&#xff08;批量操作中&#xff09; 可能是在没事务的情况下&#xff0c;利用了数据库的隐式提交 事务配置说明 通常情况下我们的Spring Component扫…

Ocelot统一权限验证

Ocelot作为网关&#xff0c;可以用来作统一验证&#xff0c;接上一篇博客Ocelot网关&#xff0c;我们继续 前一篇&#xff0c;我们创建了OcelotGateway网关项目&#xff0c;DemoAAPI项目&#xff0c;DemoBAPI项目&#xff0c;为了验证用户并分发Token&#xff0c;现在还需要添…

Spring Boot之程序性能监控

转载自 Spring Boot之程序性能监控 Spring Boot特别适合团队构建各种可快速迭代的微服务&#xff0c;同时为了减少程序本身监控系统的开发量&#xff0c;Spring Boot提供了actuator模块&#xff0c;可以很方便的对你的Spring Boot程序做监控。 1. actuator接口说明 Spring B…