基于STM32实现MQTT

最近一个项目中要用到MQTT或者CoAP,比较了两个的优缺点后。最后选择了MQTT,由于是第一次接触这个协议,在学习中遇到了不少的坑,所以分享出来。第一次写博客,有错勿怪。

1、MQTT协议

    MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

   协议详情: MQTT Version 3.1.1(英文版)

                    (中文版)

2、STM32实现的自我理解

   既然是一个协议,所以有一个基本的物理网络层就可以实现

   将就手上的一个ESP8266无线wifi串口模块,一个STM32的板子。也就可以完成MQTT的测试了。

   一般来说在协议中扮演的是一个客户端。主要的工作就是连接服务器、订阅消息、发送消息几个功能。最开始我在网上找了MQTT的客户端代码包,打开一看就蒙了。太多、太杂了,完全大于了我们的需求。所以我自己通过抓包对着协议写了一个数据包生成代码。

3、测试前准备

   MQTT服务器:我用的是Apollo Console(账号默认为:admin 密码默认:password 后面连接时要用)

   MQTT客户端: 通信猫。


   具体怎么用大家百度。。。

4、代码实现

4.1、固定报头 Fixed header

类型:

标志位:

根据这个可以写一个生成固定头的函数

#define		MQTT_TypeCONNECT							1//请求连接  
#define		MQTT_TypeCONNACK							2//请求应答  
#define		MQTT_TypePUBLISH							3//发布消息  
#define		MQTT_TypePUBACK								4//发布应答  
#define		MQTT_TypePUBREC								5//发布已接收,保证传递1  
#define		MQTT_TypePUBREL								6//发布释放,保证传递2  
#define		MQTT_TypePUBCOMP							7//发布完成,保证传递3  
#define		MQTT_TypeSUBSCRIBE						        8//订阅请求  
#define		MQTT_TypeSUBACK								9//订阅应答  
#define		MQTT_TypeUNSUBSCRIBE					                10//取消订阅  
#define		MQTT_TypeUNSUBACK							11//取消订阅应答  
#define		MQTT_TypePINGREQ							12//ping请求  
#define		MQTT_TypePINGRESP							13//ping响应  
#define		MQTT_TypeDISCONNECT 					                14//断开连接  
unsigned char GetDataFixedHead(unsigned char MesType,unsigned char DupFlag,unsigned char QosLevel,unsigned char Retain)
{
	unsigned char dat = 0;
	dat = (MesType & 0x0f) << 4;
	dat |= (DupFlag & 0x01) << 3;
	dat |= (QosLevel & 0x03) << 1;
	dat |= (Retain & 0x01);
	return dat;
}

4.2、连接

连接时抓的包的:


根据协议分析

固定报头 Fixed header

也就是数据包前两个10 21
后面是可变头:协议名、协议级别、连接标志、清除会话、遗嘱标志、遗嘱、遗嘱保留、用户名标志、密码标志、保持连接。
然后是有效载荷:客户端标识符、遗嘱主题、遗嘱消息、用户名、密码。
#define		MQTT_StaCleanSession					1	//清理会话 
#define		MQTT_StaWillFlag					0	//遗嘱标志
#define		MQTT_StaWillQoS						0	//遗嘱QoS连接标志的第4和第3位。
#define		MQTT_StaWillRetain					0	//遗嘱保留
#define		MQTT_StaUserNameFlag					1	//用户名标志 User Name Flag
#define		MQTT_StaPasswordFlag					1	//密码标志 Password Flag
#define		MQTT_KeepAlive					        60       //心跳包秒数
#define		MQTT_ClientIdentifier          "test"	                         //客户端标识符 Client Identifier
#define		MQTT_WillTopic			""				//遗嘱主题 Will Topic
#define		MQTT_WillMessage		""				//遗嘱消息 Will Message
#define		MQTT_UserName			"admin"			//用户名 User Name
#define		MQTT_Password			"password"	//密码 Password
void GetDataConnet(unsigned char *buff)//获取连接的数据包正确连接返回20 02 00 00
{
	unsigned int i,len,lennum = 0;
	unsigned char *msg;
	buff[0] = GetDataFixedHead(MQTT_TypeCONNECT,0,0,0);
	buff[2] = 0x00;
	buff[3] = 0x04;
	buff[4] = 'M';
	buff[5] = 'Q';
	buff[6] = 'T';
	buff[7] = 'T';
	buff[8] = 0x04;//协议级别 Protocol Level
	buff[9] = 0 | (MQTT_StaCleanSession << 1) | (MQTT_StaWillFlag << 1)
		    | (MQTT_StaWillQoS << 3) | (MQTT_StaWillRetain << 5) 
		    | (MQTT_StaPasswordFlag << 6) |(MQTT_StaUserNameFlag << 7);//连接标志
	buff[10] = MQTT_KeepAlive >> 8;
	buff[11] = MQTT_KeepAlive;
	len = strlen(MQTT_ClientIdentifier);
	buff[12] = len >> 8;
	buff[13] = len;
	msg = MQTT_ClientIdentifier;
	for(i = 0;i<len;i++)
	{
		buff[14+i] =  msg[i];
	}
	lennum += len;
	if(MQTT_StaWillFlag)
	{
		len = strlen(MQTT_WillTopic);
		buff[13 + lennum + 1] = len >> 8;
		buff[13 + lennum + 2] = len;
		lennum += 2;
		msg = MQTT_WillTopic;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
		len = strlen(MQTT_WillMessage);
		buff[12] = len >> 8;
		buff[13] = len;
		lennum += 2;
		msg = MQTT_WillMessage;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
	}
	if(MQTT_StaUserNameFlag)
	{
		len = strlen(MQTT_UserName);
		buff[13 + lennum + 1] = len >> 8;
		buff[13 + lennum + 2] = len;
		lennum += 2;
		msg = MQTT_UserName;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
		
	}
	if(MQTT_StaPasswordFlag)
	{
		len = strlen(MQTT_Password);
		buff[13 + lennum + 1] = len >> 8;
		buff[13 + lennum + 2] = len;
		lennum += 2;
		msg = MQTT_Password;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
	}
	buff[1] = 13 + lennum - 1;//最后计算长度
}

4.3、订阅主题

订阅a/b时抓的包:

/**********************
订阅主题的数据包 
Num:主题序号 (每次连接从1开始)
RequestedQoS:服务质量要求0,1或2
**********************/
void GetDataSUBSCRIBE(unsigned char *buff,const char *dat,unsigned int Num,unsigned char RequestedQoS)
{
	unsigned int i,len = 0,lennum = 0; 
	buff[0] = 0x82;
	len = strlen(dat);
	buff[2] = Num>>8;
	buff[3] = Num;
	buff[4] = len>>8;
	buff[5] = len;
	for(i = 0;i<len;i++)
	{
		buff[6+i] = dat[i];
	}
	lennum = len;
	buff[6 + lennum ] = RequestedQoS;
	buff[1] = lennum + 5;
}

4.4、发布信息

固定报头:

发送a/c主题信息为123时抓的包:



/**************************************
buff:数据包数组
dup :重发标志
qos :服务质量等级
retain:保留标志
topic:主题如“a/c”
msg:消息
************************************/
void GetDataPUBLISH(unsigned char *buff,unsigned char dup, unsigned char qos,unsigned char retain,const char *topic ,const char *msg)
{    
        unsigned int i,len=0,lennum=0;
	buff[0] = GetDataFixedHead(MQTT_TypePUBLISH,dup,qos,retain);
	len = strlen(topic);
	buff[2] = len>>8;
	buff[3] = len;
	for(i = 0;i<len;i++)
	{
		buff[4+i] = topic[i];
	}
	lennum = len;
	len = strlen(msg);
	for(i = 0;i<len;i++)
	{
		buff[4+i+lennum] = msg[i];
	}
	lennum += len;
	buff[1] = lennum + 2;
}

4.5、心跳请求

客户端发送PINGREQ报文给服务端的。

用于:

    在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。

    请求服务端发送 响应确认它还活着。

    使用网络以确认网络连接没有断开。

心跳请求抓的包:

可以看到就是发了一个C0 00

而服务器返回一个    d0 00

5、总结

    文中就讲了几个非常重要的请求格式。esp8266连接tcp服务器后就可以发送生成的数据包,就可以实现MQTT的功能。

    代码我已经测试过完全可用。

下面给完整的代码:

mqtt.h

#ifndef __MQTT_H
#define __MQTT_H	 
#include "sys.h"
#include <string.h>
#define		MQTT_TypeCONNECT							1//请求连接  
#define		MQTT_TypeCONNACK							2//请求应答  
#define		MQTT_TypePUBLISH							3//发布消息  
#define		MQTT_TypePUBACK								4//发布应答  
#define		MQTT_TypePUBREC								5//发布已接收,保证传递1  
#define		MQTT_TypePUBREL								6//发布释放,保证传递2  
#define		MQTT_TypePUBCOMP							7//发布完成,保证传递3  
#define		MQTT_TypeSUBSCRIBE						8//订阅请求  
#define		MQTT_TypeSUBACK								9//订阅应答  
#define		MQTT_TypeUNSUBSCRIBE					10//取消订阅  
#define		MQTT_TypeUNSUBACK							11//取消订阅应答  
#define		MQTT_TypePINGREQ							12//ping请求  
#define		MQTT_TypePINGRESP							13//ping响应  
#define		MQTT_TypeDISCONNECT 					14//断开连接  

#define		MQTT_StaCleanSession					1	//清理会话 
#define		MQTT_StaWillFlag							0	//遗嘱标志
#define		MQTT_StaWillQoS								0	//遗嘱QoS连接标志的第4和第3位。
#define		MQTT_StaWillRetain						0	//遗嘱保留
#define		MQTT_StaUserNameFlag					1	//用户名标志 User Name Flag
#define		MQTT_StaPasswordFlag					1	//密码标志 Password Flag
#define		MQTT_KeepAlive								60
#define		MQTT_ClientIdentifier  "Tan1"	//客户端标识符 Client Identifier
#define		MQTT_WillTopic			""				//遗嘱主题 Will Topic
#define		MQTT_WillMessage		""				//遗嘱消息 Will Message
#define		MQTT_UserName			"admin"			//用户名 User Name
#define		MQTT_Password			"password"	//密码 Password

unsigned char GetDataFixedHead(unsigned char MesType,unsigned char DupFlag,unsigned char QosLevel,unsigned char Retain);
void GetDataPUBLISH(unsigned char *buff,unsigned char dup, unsigned char qos,unsigned char retain,const char *topic ,const char *msg);//获取发布消息的数据包		 	
void GetDataSUBSCRIBE(unsigned char *buff,const char *dat,unsigned int Num,unsigned char RequestedQoS);//订阅主题的数据包 Num:主题序号 RequestedQoS:服务质量要求0,1或2
void GetDataDisConnet(unsigned char *buff);//获取断开连接的数据包
void GetDataConnet(unsigned char *buff);//获取连接的数据包正确连接返回20 02 00 00
void GetDataPINGREQ(unsigned char *buff);//心跳请求的数据包成功返回d0 00
#endif
mqtt.c
#include "mqtt.h"

unsigned char GetDataFixedHead(unsigned char MesType,unsigned char DupFlag,unsigned char QosLevel,unsigned char Retain)
{
	unsigned char dat = 0;
	dat = (MesType & 0x0f) << 4;
	dat |= (DupFlag & 0x01) << 3;
	dat |= (QosLevel & 0x03) << 1;
	dat |= (Retain & 0x01);
	return dat;
}
void GetDataConnet(unsigned char *buff)//获取连接的数据包正确连接返回20 02 00 00
{
	unsigned int i,len,lennum = 0;
	unsigned char *msg;
	buff[0] = GetDataFixedHead(MQTT_TypeCONNECT,0,0,0);
	buff[2] = 0x00;
	buff[3] = 0x04;
	buff[4] = 'M';
	buff[5] = 'Q';
	buff[6] = 'T';
	buff[7] = 'T';
	buff[8] = 0x04;//协议级别 Protocol Level
	buff[9] = 0 | (MQTT_StaCleanSession << 1) | (MQTT_StaWillFlag << 1)
							| (MQTT_StaWillQoS << 3) | (MQTT_StaWillRetain << 5) 
							|	(MQTT_StaPasswordFlag << 6) |(MQTT_StaUserNameFlag << 7);//连接标志
	buff[10] = MQTT_KeepAlive >> 8;
	buff[11] = MQTT_KeepAlive;
	len = strlen(MQTT_ClientIdentifier);
	buff[12] = len >> 8;
	buff[13] = len;
	msg = MQTT_ClientIdentifier;
	for(i = 0;i<len;i++)
	{
		buff[14+i] =  msg[i];
	}
	lennum += len;
	if(MQTT_StaWillFlag)
	{
		len = strlen(MQTT_WillTopic);
		buff[13 + lennum + 1] = len >> 8;
		buff[13 + lennum + 2] = len;
		lennum += 2;
		msg = MQTT_WillTopic;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
		len = strlen(MQTT_WillMessage);
		buff[12] = len >> 8;
		buff[13] = len;
		lennum += 2;
		msg = MQTT_WillMessage;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
	}
	if(MQTT_StaUserNameFlag)
	{
		len = strlen(MQTT_UserName);
		buff[13 + lennum + 1] = len >> 8;
		buff[13 + lennum + 2] = len;
		lennum += 2;
		msg = MQTT_UserName;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
		
	}
	if(MQTT_StaPasswordFlag)
	{
		len = strlen(MQTT_Password);
		buff[13 + lennum + 1] = len >> 8;
		buff[13 + lennum + 2] = len;
		lennum += 2;
		msg = MQTT_Password;
		for(i = 0;i<len;i++)
		{
			buff[14+lennum+i] =  msg[i];
		}
		lennum += len;
	}
	buff[1] = 13 + lennum - 1;
}
void GetDataDisConnet(unsigned char *buff)//获取断开连接的数据包
{
	buff[0] = 0xe0;
	buff[1] = 0;
}
void GetDataPINGREQ(unsigned char *buff)//心跳请求的数据包成功返回d0 00
{
	buff[0] = 0xc0;
	buff[1] = 0;
}
/*
	成功返回90 0x 00 Num RequestedQoS
*/
void GetDataSUBSCRIBE(unsigned char *buff,const char *dat,unsigned int Num,unsigned char RequestedQoS)//订阅主题的数据包 Num:主题序号 RequestedQoS:服务质量要求0,1或2
{
	unsigned int i,len = 0,lennum = 0; 
	buff[0] = 0x82;
	len = strlen(dat);
	buff[2] = Num>>8;
	buff[3] = Num;
	buff[4] = len>>8;
	buff[5] = len;
	for(i = 0;i<len;i++)
	{
		buff[6+i] = dat[i];
	}
	lennum = len;
	buff[6 + lennum ] = RequestedQoS;
	buff[1] = lennum + 5;
}
void GetDataPUBLISH(unsigned char *buff,unsigned char dup, unsigned char qos,unsigned char retain,const char *topic ,const char *msg)//获取发布消息的数据包
{
	unsigned int i,len=0,lennum=0;
	buff[0] = GetDataFixedHead(MQTT_TypePUBLISH,dup,qos,retain);
	len = strlen(topic);
	buff[2] = len>>8;
	buff[3] = len;
	for(i = 0;i<len;i++)
	{
		buff[4+i] = topic[i];
	}
	lennum = len;
	len = strlen(msg);
	for(i = 0;i<len;i++)
	{
		buff[4+i+lennum] = msg[i];
	}
	lennum += len;
	buff[1] = lennum + 2;
}

猜你喜欢

转载自blog.csdn.net/qq_39785798/article/details/80765716