当前位置:首页 » 《随便一记》 » 正文

一文带你了解 MQTT 协议(连接 ONE-NET平台)_丿轩雪 的博客

21 人参与  2022年02月18日 13:08  分类 : 《随便一记》  评论

点击全文阅读


MQTT 协议连接 ONE-NET 详解

写在前面

​  本文采用 网络调试助手 发送MQTT协议报文(16进制) 连接 ONE-NET 平台,采用的 为 MQTT v3.1.1 标准协议。带你直接 学会 MQTT 协议。

ONE-NET 端创建设备

​   由于我们需要使用 MQTT 协议 连接 ONE-NET 平台,所以我们需要先创建一个设备。

1. 进入 ONE-NET 平台,进入控制台首页,切换置旧版

2. 点击最左侧圆球,选择多协议接入

3. 我们这次采用的是 MQTT 协议,所以直接创建MQTT 协议的产品即可。

4. 联网方式选择 wifi 即可

5. 再添加设备,注意:鉴权信息就是你后面需要使用的 密码,下面的鉴权信息是 博主本人的 QQ号,有需要讨论问题可以添加!

6. 这时候我们需要记录三个连接 ONE-NET 的数据,一个是设备 ID,一个是产品 ID,一个是鉴权信息

​  设备ID 和 密码的话:就是你前面设置的鉴权信息。如果遗忘的话,可以在设备详情里查找

 最后,ONE-NET 端 的设备就创建完毕了。

通过 MQTT v3.1.1协议 编写对应的报文

​  MQTT 协议报文由三部分组成,固定报文,可变报头和有效载荷,所以我们编写 MQTT 协议报文也需要从这三方面入手。

1. 通过网络调试助手连接报文 连接 ONE-NET 端

1. 固定报头

 首先是固定报头,上述是 CONNECT 报文的固定报头,由图可知 byte1 = 10,剩余长度是可变报头+有效载荷,如下图所示,剩余长度最多为四个字节,最高位如果 为 1 ,代表下面还有 至少一个字节数。后面当所有报文都编写完后再计算剩余长度大小。

2. 可变报头

​ 如上图所示,我们可以编写出 byte1 - byte7,大概为 00 04 4D 51 54 54 04 (16进制数)

​ byte 8 表示,由于 ONE-NET 端不允许匿名登录,所以我们需要将 用户名标志,密码标志置 1,由于我们为初学者,我们只需要再掌握一个QoS服务质量即可,下面是服务质量分析图。

​  一般我们采用 最多分发一次即可。也就是 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,密码输入

​  再点击 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进制数了

 我这边为 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进制数接受,换行等。

 由图我们发送连接报文,发现返回 20 02 00 00,并且设备已经在线了,说明我们连接成功。

 CONNACK - 确认连接请求 固定报头 就为 20 02 ,也再一次证明为 连接确认请求

​  其他的报文也和这个 连接报文 差不多,可以自己阅读 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代码地址 如果可以的话,能帮我 github 上点个赞么。

 若是该文章对你有作用或是觉得文章写得还行,帮忙点点赞,三连!


点击全文阅读


本文链接:http://zhangshiyu.com/post/34923.html

报文  报头  载荷  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1