MQTT 协议连接 ONE-NET 详解
写在前面
本文采用 网络调试助手 发送MQTT协议报文(16进制) 连接 ONE-NET 平台,采用的 为 MQTT v3.1.1 标准协议。带你直接 学会 MQTT 协议。
一、ONE-NET 端创建设备
由于我们需要使用 MQTT 协议 连接 ONE-NET 平台,所以我们需要先创建一个设备。
1. 进入 ONE-NET 平台,进入控制台首页,切换置旧版
![](https://i-blog.csdnimg.cn/blog_migrate/9f097f3fc58992ec26575700d7f3c70f.png)
2. 点击最左侧圆球,选择多协议接入
![](https://i-blog.csdnimg.cn/blog_migrate/392749065009808574fae2303b367854.png)
3. 我们这次采用的是 MQTT 协议,所以直接创建MQTT 协议的产品即可。
![](https://i-blog.csdnimg.cn/blog_migrate/7843215368dfc5744cbeec2889f60776.png)
4. 联网方式选择 wifi 即可
![](https://i-blog.csdnimg.cn/blog_migrate/667bc4a773011a1ba653ee5c10f0d48d.png)
5. 再添加设备,注意:鉴权信息就是你后面需要使用的 密码,下面的鉴权信息是 博主本人的 QQ号,有需要讨论问题可以添加!
![](https://i-blog.csdnimg.cn/blog_migrate/2fad719189c802aa158ddb679441670c.png)
6. 这时候我们需要记录三个连接 ONE-NET 的数据,一个是设备 ID,一个是产品 ID,一个是鉴权信息
![](https://i-blog.csdnimg.cn/blog_migrate/da0beb845ac63b6c90b0963d0cd98504.png)
设备ID 和 密码的话:就是你前面设置的鉴权信息。如果遗忘的话,可以在设备详情里查找
![](https://i-blog.csdnimg.cn/blog_migrate/9286850e5f094c4f51a3beb4f7378ec3.png)
最后,ONE-NET 端 的设备就创建完毕了。
二、通过 MQTT v3.1.1协议 编写对应的报文
MQTT 协议报文由三部分组成,固定报文,可变报头和有效载荷,所以我们编写 MQTT 协议报文也需要从这三方面入手。
1. 通过网络调试助手连接报文 连接 ONE-NET 端
1. 固定报头
![](https://i-blog.csdnimg.cn/blog_migrate/42b69e34c26ccbf41d87db866a639dba.png)
首先是固定报头,上述是 CONNECT 报文的固定报头,由图可知 byte1 = 10,剩余长度是可变报头+有效载荷,如下图所示,剩余长度最多为四个字节,最高位如果 为 1 ,代表下面还有 至少一个字节数。后面当所有报文都编写完后再计算剩余长度大小。
![](https://i-blog.csdnimg.cn/blog_migrate/c27cda0209bb198239d9c5208c9526ab.png)
2. 可变报头
![](https://i-blog.csdnimg.cn/blog_migrate/3be01a1a242421764d3d3fed168127e7.png)
如上图所示,我们可以编写出 byte1 - byte7,大概为 00 04 4D 51 54 54 04 (16进制数)
byte 8 表示,由于 ONE-NET 端不允许匿名登录,所以我们需要将 用户名标志,密码标志置 1,由于我们为初学者,我们只需要再掌握一个QoS服务质量即可,下面是服务质量分析图。
![](https://i-blog.csdnimg.cn/blog_migrate/008f47ad04b250763678f63c33f90437.png)
一般我们采用 最多分发一次即可。也就是 QoS = 0 ,bit2 bit1 为 00
byte 9 和 byte 10 为 保活时间,我这边采取 100 的 保活时间,变成16进制数也就是 64
所以 byte9 和byte 10 分别为 00 64
所以固定报头和可变报头为:10 xx(未计算剩余长度) 00 04 4d 51 54 54 04 c0 00 64
3. 有效载荷
我们需要验证 设备号,用户名(产品 ID)和密码(鉴权信息)。
由于报文为16进制数,我们可以通过网络助手快速计算出这些数据的16进制数和字节长度。
先选择 UDP 形式,连接远程主机,并且分别把设备 ID,产品ID,密码输入
![](https://i-blog.csdnimg.cn/blog_migrate/e9690b03ab7be441670bcb17db223da7.png)
再点击 16 进制数,将该数值转化为 16 进制数。发送的数值为 9 ,注意:要转换成16进制数,换算成 16 进制数 也是 9。所以不必对其进行额外的 进制转换。
所以有效载荷就为:00 09 37 38 39 35 34 36 38 30 35 00 06 34 35 38 39 34 35 00 09 31 33 36 39 32 38 38 33 31 (先输入 设备号 大小 00 - 09 再输入其设备号,后续一样操作)
最后将 可变报头和有效载荷 一起输入到网络调试助手计算字节大小,注意:复制数值前先勾选16进制数,因为我们已经将前面的转换为16进制数了
![](https://i-blog.csdnimg.cn/blog_migrate/ec3c89f7ed62d59e0b6d2351d927f89c.png)
我这边为 40 ,转化为 16进制数为 28 ,所以我的剩余长度就为 28
CONNECT连接报文总共为:10 28 00 04 4d 51 54 54 04 c0 00 64 00 09 37 38 39 35 34 36 38 30 35 00 06 34 35 38 39 34 35 00 09 31 33 36 39 32 38 38 33 31
这时候我们将 网络调试助手 换成 TCP Client 端,远程主机地址为:183.230.40.39 :6002,并且在复制前先勾选16进制发送,和16进制数接受,换行等。
![](https://i-blog.csdnimg.cn/blog_migrate/5fc9be0a9e89a65ceb18a21d95fbefe3.png)
由图我们发送连接报文,发现返回 20 02 00 00,并且设备已经在线了,说明我们连接成功。
![](https://i-blog.csdnimg.cn/blog_migrate/2f5d8ab9d586214a5ed3f04d2ee6f929.png)
CONNACK - 确认连接请求 固定报头 就为 20 02 ,也再一次证明为 连接确认请求
![](https://i-blog.csdnimg.cn/blog_migrate/4433f7c9cdf46856b1b2968d8cb1e8cc.png)
其他的报文也和这个 连接报文 差不多,可以自己阅读 MQTT v3.1.1 标准协议。
下面是 Keil5 编写的 MQTT 连接报文
uint8 MQTT_PacketConnect(const int8 *user, const int8 *password, const int8 *devid,
uint16 cTime, uint1 clean_session, uint1 qos,
const int8 *will_topic, const int8 *will_msg, int32 will_retain,
MQTT_PACKET_STRUCTURE *mqttPacket)
{
//flags 用于接收 byte 8 ,判断用户名,密码,服务质量,遗嘱信息等
uint8 flags = 0;
uint8 will_topic_len = 0;
uint16 total_len = 15;
int16 len = 0, devid_len = strlen(devid); //设备号
if(!devid) //如果设备号为空,直接退出
return 1;
total_len += devid_len + 2; // + 2 表示需要使用两个字节表示 设备号的起始和结束位置
//判断断线后,是否清理离线消息:1-清理, 0-不清理
if(clean_session) //默认为 0 就好
{
flags |= MQTT_CONNECT_CLEAN_SESSION; //MQTT_CONNECT_CLEAN_SESSION = 0x02,根据 byte8 第2位为 1 的话代表清理离线消息
}
//异常掉线是,服务器发送的 Topic
if(will_topic) //默认为 NULL 即可
{
flags |= MQTT_CONNECT_WILL_FLAG;
will_topic_len = strlen(will_topic);
total_len += 4 + will_topic_len + strlen(will_msg);
}
//qos 级别,主要用于 publish 消息
switch((unsigned char)qos)
{
case MQTT_QOS_LEVEL0:
flags |= MQTT_CONNECT_WILL_QOS0; //最多一次
break;
case MQTT_QOS_LEVEL1:
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS1); //最少一次
break;
case MQTT_QOS_LEVEL2:
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS2); //只有一次
break;
default:
return 2;
}
//主要用于 publish 消息,代表服务器要保留这次推送的信息,如果有新的订阅者,就把该消息推送给它
if(will_retain)
{
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_RETAIN);
}
//ONE-NET 不允许匿名登录,需要判断
if(!user || !password)
{
return 3;
}
flags |= MQTT_CONNECT_USER_NAME | MQTT_CONNECT_PASSORD; //byte8 判断是否有用户名,密码
total_len += strlen(user) + strlen(password) + 4; //总长度加 4
//分配新内存
MQTT_NewBuffer(mqttPacket, total_len); //本质是 malloc 创建
if(mqttPacket->_data == NULL) //由于创建的时候,将data全部置0,所以做个判断
return 4;
memset(mqttPacket->_data, 0, total_len); //清空下 data,保证无数据
/*************************************固定头部******************************************/
//byte1---------------------连接报文的话为 10 ------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_CONNECT << 4;//1 左移四位
//byte2----------------------剩余长度计算----------------------------------------------
len = MQTT_DumpLength(total_len - 5, mqttPacket->_data + mqttPacket->_len);
if(len < 0)
{
MQTT_DeleteBuffer(mqttPacket);
return 5;
}
else
mqttPacket->_len += len; //添加剩余长度字节大小 len 大小为0-4
/*************************************可变报头************************************/
//可变报头----------------------协议长度和协议名(固定的)---------------------------------
mqttPacket->_data[mqttPacket->_len++] = 0;
mqttPacket->_data[mqttPacket->_len++] = 4;
mqttPacket->_data[mqttPacket->_len++] = 'M';
mqttPacket->_data[mqttPacket->_len++] = 'Q';
mqttPacket->_data[mqttPacket->_len++] = 'T';
mqttPacket->_data[mqttPacket->_len++] = 'T';
//----------------------protocol level 4(固定)---------------------------------------
mqttPacket->_data[mqttPacket->_len++] = 4;
//byte8----------------------判断是否存在用户名,密码等标志-----------------------------
mqttPacket->_data[mqttPacket->_len++] = flags;
//byte9,byte10---------------保活时间----------------------------------------
//#define MOSQ_MSB(A) (uint8)((A & 0xFF00) >> 8)
//#define MOSQ_LSB(A) (uint8)(A & 0x00FF)
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(cTime);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(cTime);
/*************************************有效载荷*****************/
//有效载荷----------------------------devid长度,devid---------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(devid_len);//
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(devid_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, devid, devid_len);
mqttPacket->_len += devid_len;
//有效载荷----------------------------will_flag 和 will_msg(默认为 NULL 0)-------------
//不执行下列代码
if(flags & MQTT_CONNECT_WILL_FLAG)
{
unsigned short mLen = 0;
if(!will_msg)
will_msg = "";
mLen = strlen(will_topic);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_topic, mLen);
mqttPacket->_len += mLen;
mLen = strlen(will_msg);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_msg, mLen);
mqttPacket->_len += mLen;
}
//有效载荷----------------------------use用户名-----------------------------------------
if(flags & MQTT_CONNECT_USER_NAME) //判断用户名这一位是否置 1,置1代表存在用户名
{
unsigned short user_len = strlen(user);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(user_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(user_len);
//使用 strncat 函数添加用户名
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, user, user_len);
mqttPacket->_len += user_len;
}
//有效载荷----------------------------password密码-------------------------------------
if(flags & MQTT_CONNECT_PASSORD) //判断密码这一位是否置 1,置1代表存在密码
{
unsigned short psw_len = strlen(password);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(psw_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(psw_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, password, psw_len);
mqttPacket->_len += psw_len;
}
return 0;
}
最后
本专栏完结了,最后给大家提供下所有的源代码。有需要自取。
如果可以的话,能帮我 github 上加个星么。
若是该文章对你有作用或是觉得文章写得还行,帮忙点点赞,三连!
![](https://i-blog.csdnimg.cn/blog_migrate/447562d103f414f59b812504a3413105.jpeg)