W5500-EVB-PICO进行MQTT连接订阅发布教程(十二)
前言
上一章我们用开发板通过SNTP协议获取网络协议,本章我们介绍一下开发板通过配置MQTT连接到服务器上,并且订阅和发布消息。
什么是MQTT?
MQTT是一种轻量级的消息传输协议,旨在物联网(IoT)应用中实现设备间的可靠通信。它使用发布-订阅模式,其中包括一个MQTT服务端(代理或服务器)和多个MQTT客户端之间的通信。MQTT协议具有以下特点:
- 轻量级:MQTT协议设计简单,协议头部开销小,适用于资源受限的设备和网络。
- 低带宽消耗:MQTT采用二进制编码,有效地利用网络带宽。
- 异步通信:客户端可以随时发布和订阅消息,无需等待对方的响应。
- 发布-订阅模式:消息发布者将消息发布到特定的主题,而订阅者则订阅感兴趣的主题。这种模式支持松耦合的通信和灵活的消息传递。
报文介绍
报文格式
MQTT控制报文由三部分组成,分别是固定报头,可变报头,有效载荷。

固定报头

固定报头最少由两个字节组成,第一个字节的7-4位为协议类型,3-0位为标志位,从第二个字节开始为剩余长度(包括可变报头和有效载荷的长度)
协议类型具体定义可参考下表:


标志位可以参考下表:

其中:
DUP1 = 控制报文的重复分发标志
QoS2 = PUBLISH 报文的服务质量等级
RETAIN3 = PUBLISH 报文的保留标志
协议类型示例如下表:

剩余长度字段最多四个字节,最少一个字节,具体长度如下表所示:

其中,每个字节的6-0位用于编码数据,第7位是标志位,为1则表示下一个字节也是剩余长度字段。
可变报头
某些控制报文包含可变报头,它在固定报头(Fixed header)和有效载荷(Payload)之间。每个协议的可变报头都不一样。
其中大多数协议都会有的字段是报文标识符。
可变报头在各个控制报文的详细内容中再展开讲解。
有效载荷
有效载荷是除控制报文格式以外的有效信息,CONNECT、PUBLISH、SUBSCRIBE等需要传递有效信息的协议帧都需要。
实例讲解
MQTT报文的具体格式可以参考文档:MQTT Version 3.1.1 (oasis-open.org)

连接MQTT服务器(客户端->服务器)
(以下皆为HEX格式)
//固定报头
10 21(剩余33个字节)
//可变报头
00 04 4D 51 54 54 04 C2 00 3C
//clientid,长度8字节,文本内容为clientid
00 08 63 6C 69 65 6E 74 69 64
//用户名,长度4字节,文本内容为MQTT
00 04 4D 51 54 54
//密码,长度5字节,文本内容为w5500
00 05 77 35 35 30 30
确认连接(服务器->客户端)
//连接成功,会话为新会话
20 02 00 00
订阅主题(客户端->服务器)
//固定报头,剩余长度10字节
82 0A
//可变报头
00 01
//有效载荷(长度5字节,内容为topic,qos为0)
00 05 74 6F 70 39 63 00
确认订阅(服务器->客户端)
//固定报头,剩余长度3字节
90 03
//可变报头
00 01
//有效载荷,回复订阅qos为0
00
发布消息(qos0)
//固定报头,qos0消息,非重传,非保留,剩余长度10字节
30 14
//可变报头,5个字节的主题“topic”
00 05 74 6F 70 69 63
//有效载荷“message”
6D 65 73 73 61 67 65
连接方式
开发板和主机都接在路由器LAN口
连接MQTTX服务器测试
相关代码
我们打开例程中的mqtt_client.c文件,首先可以看到,我们定义了MQTT协议的收发报文缓存和MQTT所使用的socket号
#define MQTT_SEND_BUFF_SIZE 2048 // MQTT协议发送报文缓存大小
#define MQTT_RECV_BUFF_SIZE 2048 // MQTT协议接收报文缓存大小
#define MQTT_SOCKET 1 // MQTT使用的SOCKET号uint8_t mqtt_send_buff[MQTT_SEND_BUFF_SIZE] = {0}; // MQTT协议发送报文缓存
uint8_t mqtt_recv_buff[MQTT_RECV_BUFF_SIZE] = {0}; // MQTT协议接收报文缓存
然后定义一个结构体来存放连接参数和订阅发布主题参数
// MQTT连接和订阅参数结构体
typedef struct MQTTCONNECTION
{uint8_t server_ip[4];int port;char clientid[1024];char username[1024];char passwd[1024];char pubtopic[255];char subtopic[255];int QOS;
} mqttconn;// MQTT连接和订阅参数
mqttconn mqtt_params = {.server_ip = {54, 244, 173, 190},.port = 1883,.clientid = "9a1d7719a8ac40d29311f26c5c5469dc",.username = "mqtt_username",.passwd = "123456",.pubtopic = "W5500",.subtopic = "W5500",.QOS = 0,
};
网络地址参数如下
static wiz_NetInfo net_info = {.mac = {0x00, 0x08, 0xdc, 0x16, 0xed, 0x2e},.ip = {192, 168, 1, 20},.sn = {255, 255, 255, 0},.gw = {192, 168, 1, 1},.dns = {8, 8, 8, 8},.dhcp = NETINFO_STATIC};
并定义了三个全局变量用来存放连接MQTT的信息
MQTTClient c = {0}; // MQTT客户端连接信息结构体
Network n = {0}; // 网络信息结构体
int connOK; //连接状态
此外,还需定义四个函数
首先是一个1ms的循环定时器回调函数,在这个回调函数中,我们必须把mqtt_interface.c库文件中的MilliTimer_Handler()函数加入到我们的1ms定时器回调函数中。
bool repeating_timer_callback(struct repeating_timer *t)
{MilliTimer_Handler();return true;
}
其次是mqtt初始化函数,在这个函数中,我们连接并且订阅主题,最后发布一条消息上去。
void mqtt_init(void)
{int ret;MQTTPacket_connectData data = MQTTPacket_connectData_initializer;NewNetwork(&n, MQTT_SOCKET);ConnectNetwork(&n, mqtt_params.server_ip, 1883);MQTTClientInit(&c, &n, 1000, mqtt_send_buff, MQTT_SEND_BUFF_SIZE, mqtt_recv_buff, MQTT_RECV_BUFF_SIZE);data.willFlag = 0;data.MQTTVersion = 3;data.clientID.cstring = mqtt_params.clientid;data.username.cstring = mqtt_params.username;data.password.cstring = mqtt_params.passwd;data.keepAliveInterval = 30;data.cleansession = 1;// 连接mqtt服务器,如果连接失败则继续重连connOK = MQTTConnect(&c, &data);printf("Connected:%s\r\n", connOK == 0 ? "success" : "failed");while (connOK){sleep_ms(50);connOK = MQTTConnect(&c, &data);printf("Connected:%s\r\n", connOK == 0 ? "success" : "failed");}// 订阅主题,如果订阅失败则继续订阅ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.QOS, messageArrived);printf("Subscribing to %s\r\n", mqtt_params.subtopic);printf("Subscribed:%s\r\n", ret == 0 ? "success" : "failed");while (ret){sleep_ms(50);ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.QOS, messageArrived);printf("Subscribing to %s\r\n", mqtt_params.subtopic);printf("Subscribed:%s\r\n", ret == 0 ? "success" : "failed");}sleep_ms(50);// 发布消息MQTTMessage pubmessage = {.qos = QOS0,.retained = 0,.dup = 0,.id = 0,};pubmessage.payload = "hello mqtt!";pubmessage.payloadlen = strlen(pubmessage.payload);MQTTPublish(&c, mqtt_params.pubtopic, &pubmessage);printf("TX:%s\r\n", pubmessage.payload);
}
然后就是消息回调函数,服务器下发的消息都会进入该函数中进行处理。
void messageArrived(MessageData *md)
{unsigned char messagebuffer[512];MQTTMessage *message = md->message;if (0)//展示qos等级{memcpy(messagebuffer, (char *)message->payload, (int)message->payloadlen);*(messagebuffer + (int)message->payloadlen + 1) = '\n';printf("%s\r\n", messagebuffer);}if (0)//展示qos等级printf("%.*s", (int)message->payloadlen, (char *)message->payload);elseprintf("%s%.*s%s%s", "Rx:", (int)message->payloadlen, (char *)message->payload, mqtt_params.QOS, "\r\n");
}
最后就是mqtt保活函数,该函数需要放在主函数的主循环中,否则可能导致保活失败
void keep_mqtt(void)
{if (MQTTYield(&c, 30)){mqtt_init();}
}
在主函数中,我们只需要初始化网络信息和接口,然后开启1ms循环定时器,最后初始化mqtt,然后把mqtt保活函数放入主循环中即可。
int main()
{struct repeating_timer timer;stdio_init_all();sleep_ms(3000);printf("W5500 mqtt example.\r\n");wizchip_initialize(); // SPI初始化以及链路状态检测wizchip_setnetinfo(&net_info); // 设置网络地址信息print_network_information(net_info); // 打印网络地址信息add_repeating_timer_ms(1, repeating_timer_callback, NULL, &timer); // 开启1ms循环定时器mqtt_init(); // mqtt连接函数while (true){keep_mqtt(); // mqtt保活}
}
测试效果
将程序编译烧录后,打开串行监视器,可以看到,成功连接并且订阅上主题,还发布了一条信息。

在MQTTX上我们也能收到开发板发布的消息,我们在MQTTX发布一条消息出去。开发板也同样能收到。

相关连接
本章例程链接:mqtt_client example
相关文章:

W5500-EVB-PICO进行MQTT连接订阅发布教程(十二)
前言 上一章我们用开发板通过SNTP协议获取网络协议,本章我们介绍一下开发板通过配置MQTT连接到服务器上,并且订阅和发布消息。 什么是MQTT? MQTT是一种轻量级的消息传输协议,旨在物联网(IoT)应用中实现设备…...

90、00后严选出的数据可视化工具:奥威BI工具
90、00后主打一个巧用工具,绝不低效率上班,因此当擅长大数据智能可视化分析的BI数据可视化工具出现后,自然而然地就成了90、00后职场人常用的数据可视化工具。 奥威BI工具三大特点,让职场人眼前一亮! 1、零编程&…...

删除maven中出现.lastUpdate结尾的文件
出现 .lastupdate 结尾的文件的原因:由于网络原因没有将maven的依赖下载完整. 解决方案: 1) 删除所有以 .lastupdate 结尾的文件 A) 1.切换到maven本地仓库 B)2.在当前目录打开cmd命令行(shift右键-->在此处打开命令窗口 或 直接在当前文件路径上敲cmd 或 右键-->…...

Cannot assign to read only property ‘exports‘ of object ‘#<Object>‘
webpack打包js文件中不允许混用import和module.exports。 方式①:babel.config.js添加sourceType: {"presets": [...],"sourceType": "unambiguous" }方式②:安装babel-plugin-transform-es2015-modules-commonjs npm …...

Dockerfile中编译、打包、部署spring boot项目
1、Dockerfile 1.1、什么是Dockerfile Dockerfile是自动构建docker镜像的配置文件,将镜像构建过程通过指令的方式定义在Dockerfile中。配合docker build命令行可以实现自动化的Docker镜像的构建。 1.2、Dockerfile语法解析 我们在学习一门语言或文档语法的时候&am…...

微型计算机原理知识点总结(一)
目录 一.微型计算机 二.微型计算机系统 1.微型计算机硬件系统 冯诺依曼体系结构 总线 (1)微处理器(CPU) 运算器 控制器 内部寄存器 (2)存储器 1.基本概念 2.内存的操作 3.内存的分类 (3)I/O接口与输入/输出设备 2.微型计算机软件系统 (1)系统软件 操作系统 …...

【postgresql 基础入门】psql客户端的使用方法
psql 客户端使用 专栏内容: postgresql内核源码分析手写数据库toadb并发编程 开源贡献: toadb开源库 个人主页:我的主页 管理社区:开源数据库 座右铭:天行健,君子以自强不息;地势坤…...

QTcpSocket发送数据方法
文章目录 一、简介二、write(const char *, qint64)三、isValid() 一、简介 本文主要记录QTcpSocket的write(const char *, qint64)和isValid()。 二、write(const char *, qint64) 概念:在QTcpSocket中,使用write(char* data, int size)函数将指定长…...

select 语句执行顺序
sql 样例 select t_students.name as 姓名, sum(case when b.cname语文 then c.sc_val else 0 end) As 语文 ,sum(case when b.cname数学 then c.sc_val else 0 end) As 数学,sum(case when b.cname英语 then c.sc_val else 0 end) As 英语, From t_students a, t_corses b, t…...

PMD 检查java代码:避免将内部数组直接返回给调用者(MethodReturnsInternalArray)
https://docs.pmd-code.org/pmd-doc-6.55.0/pmd_rules_java_bestpractices.html#methodreturnsinternalarray 直接将内部数组暴露给调用者破坏了封装性,因为调用者可能在拥有内部数组的对象外部更改或者删除数组的元素。返回内部数组的拷贝会更加安全。 例如&…...

ActiveMQ配置初探
文章目录 配置wrapper相关配置wrapper是干什么用的MQ的运行内存修改【需修改】修改内容题外话 wrapper.log配置【需修改】引起的问题优化方式 activemq.xml相关配置官网介绍配置管理后台的认证授权【建议修改】配置broker【根据自己需求更改】配置允许jmx监控关闭消息通知持久化…...

【官方中文文档】Mybatis-Spring #示例代码
示例代码 提示 查看 JPetstore 6 demo 了解如何在完整的 Web 应用服务器上使用 Spring。 您可以在 MyBatis-Spring 的 代码仓库 中查看示例代码: 所有示例都能在 JUnit 5 下运行。 示例代码演示了事务服务从数据访问层获取域对象的典型设计。 FooService.java …...

python二级例题
请编写程序,生成随机密码。具体要求如下: (1)使用 rand…...

【java】【项目实战】[外卖九]项目优化(缓存)
目录 一、问题说明 二、环境搭建 2.1 Git管理代码 2.1.1 创建本地仓库 2.1.2 创建远程仓库 2.1.3 创建分支--》推送到远程仓库 2.2 maven坐标 2.3 配置文件application.yml 2.4 配置类RedisConfig 三、缓存短信验证码 3.1 实现思路 3.2 代码改造 3.2.1 UserContro…...

Scala集合常用函数与集合计算简单函数,高级计算函数Map和Reduce等
Scala集合常用函数与集合计算简单函数 1.Scala集合常用函数 基本属性和常用操作 1.常用函数: (1) 获取集合长度(2) 获取集合大小(3) 循环遍历(4) 迭代器(…...

You must install at least one postgresql-client-<version> package
使用主机上的映射端口来连接到 PostgreSQL 数据库。例如,使用以下命令连接到数据库: psql -h localhost -p 5432 -U postgres出现下面的问题: 分析: 如果您在运行 psql 命令时遇到错误消息 You must install at least one pos…...

爬虫源码---爬取自己想要看的小说
前言: 小说作为在自己空闲时间下的消遣工具,对我们打发空闲时间很有帮助,而我们在网站上面浏览小说时会被广告和其他一些东西影响我们的观看体验,而这时我们就可以利用爬虫将我们想要观看的小说下载下来,这样就不会担…...

【AGC】云数据库API9开发问题汇总
【问题描述】 云数据库HarmonyOS API9 SDK已经推出了一段时间了,下面为大家汇总一些在集成使用中遇到的问题和解决方案。 【问题分析】 1. 报错信息:数据库初始化失败:{“message”:“The object type list and permission …...

ASP.NET Core IOC容器
//IOC容器支持依赖注入{ServiceCollection serviceDescriptors new ServiceCollection();serviceDescriptors.AddTransient<IMicrophone, Microphone>();serviceDescriptors.AddTransient<IPower, Power>();serviceDescriptors.AddTransient<IHeadphone, Headp…...
入门力扣自学笔记277 C++ (题目编号:42)(动态规划)
42. 接雨水 题目: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组…...

SwiftUI实现iPad多任务分屏
1. 概述 iPadOS引入了多任务分屏功能,使用户能够同时在一个屏幕上使用多个应用程序。这为用户提供了更高效的工作环境,可以在同一时间处理多个任务。 iPad多任务分屏有两种常见的模式:1/2分屏和Slide Over(滑动覆盖)…...

maven依赖,继承
依赖的范围 compile引入的依赖 对main目录下的代码有没有效,main目录下的代码能不能用compile引入的依赖中的类等 以test引入的依赖,在main中是否可以使用 provided(已提供),有了就不要带到服务器上,打包…...

仿`gRPC`功能实现像调用本地方法一样调用其他服务器方法
文章目录 仿gRPC功能实现像调用本地方法一样调用其他服务器方法 简介单体架构微服务架构RPCgPRC gRPC交互逻辑服务端逻辑客户端逻辑示例图 原生实现仿gRPC框架编写客户端方法编写服务端方法综合演示 仿 gRPC功能实现像调用本地方法一样调用其他服务器方法 简介 在介绍gRPC简介…...

分布式环境下的数据同步
一般而言elasticsearch负责搜索(查询),而sql数据负责记录(增删改),elasticsearch中的数据来自于sql数据库,因此sql数据发生改变时,elasticsearch也必须跟着改变,这个就是…...

无涯教程-Flutter - 数据库
SQLite" class"css-1occaib">SQLite数据库是基于事实和标准SQL的嵌入式数据库引擎,它是小型且经过时间考验的数据库引擎,sqflite软件包提供了许多函数,可以有效地与SQLite数据库一起使用,它提供了操作SQLite数据…...

算法笔记:平衡二叉树
1 介绍 平衡二叉树(AVL树)是一种特殊的二叉搜索树(BST),它自动确保树保持低高度,以便实现各种基本操作(如添加、删除和查找)的高效性能。 ——>时间都维持在了O(logN)它是一棵空…...

redis 通用命令
目录 通用命令是什么 SET & GET keys EXISTS DEL EXPIRE TTL redis 的过期策略 定时器策略 基于优先级队列定时器 基于时间轮的定时器 TYPE 通过 redis 客户端和 redis 服务器交互。 所以需要使用 redis 的命令,但是 redis 的命令非常多。 通用命令…...

Pycharm配置及使用Git教程
文章目录 1. 安装PyCharm2. 安装Git3. 在PyCharm中配置Git插件4. 连接远程Gtilab仓库5. Clone项目代码6. 将本地文件提交到远程仓库6.1 git add6.2 git commit6.3 git push6.4 git pull 平时习惯在windows下开发,但是我们又需要实时将远方仓库的代码clone到本地&…...

CSS transition 过渡
1 前言 水平居中、垂直居中是前端面试百问不厌的问题。 其实现方案也是多种多样,常叫人头昏眼花。 水平方向可以认为是内联方向,垂直方向认为是块级方向。 下面介绍一些常见的方法。 2 内联元素的水平垂直居中 首先,常见内联元素有&…...

Unity中Shader的UV扭曲效果的实现
文章目录 前言一、实现的思路1、在属性面板暴露一个 扭曲贴图的属性2、在片元结构体中,新增一个float2类型的变量,用于独立存储将用于扭曲的纹理的信息3、在顶点着色器中,根据需要使用TRANSFORM_TEX对Tilling 和 Offset 插值;以及…...