当前位置:   article > 正文

模块学习4:(2)MQTT协议连接、发布、订阅、心跳、断链等分析和代码实现,并且通过mqtt.fx连接服务器,使用wireshark抓包分析mqtt实现过程_连接报文中,固定报头长度暂时先=1

连接报文中,固定报头长度暂时先=1


上一篇对于MQTT的相关笔记有一些粗糙,加上最近的开发中,也发现了一些问题,我把整个代码连接结构进行了一个重写,看着会更加的简洁易懂,好进行后续扩展,在这一块做一个总结。

一、MQTT控制报文的结构

简单来说就是:固定报头+可变报头+有效负载

(1)固定报头(类型/标志 + 剩余长度)

固定报头
第一个字节的高四位指的是控制报文类型,有如下16种类型:

固定报头类型
第一个字节的低四位指的是标志位,和上面的类型是对应的,现在看这两个表有点抽象,但是下面涉及到具体报文,再回头看就豁然开朗:
标志位

剩余长度(这个要注意下,要注意它的计算方法,有一点特殊)

剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。
剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节编码。更大的值按下面 的方式处理。低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码128个数值和一个延续位(continuation bit)。剩余长度字段最大4个字节。

注释
例如,十进制数64会被编码为一个字节,数值是64,十六进制表示为0x40,。十进制数字 321(=65+2*128)被编码为两个字节,最低有效位在前。第一个字节是 65+128=193(模2,余65,2在后,65在前,然后65的最高位置1,然后就是C1)。注意最高位为1表示后面至少还有一个字节。第二个字节是2。所以321 == 0xC1 0x02

剩余长度字段的大小:
在这里插入图片描述
注释:
这允许应用发送最大256MB(268,435,455)大小的控制报文。这个数值在报文中的表示 是:0xFF,0xFF,0xFF,0x7F。

所以计算剩余长度代码如下:

//伪代码如下
do encodedByte = X MOD 128 
	X = X DIV 128 // if there are more data to encode, set the top bit of this byte 
	if ( X > 0 ) 
		encodedByte = encodedByte OR 128 
	endif
		'output' encodedByte 
while ( X > 0 )

//C语言代码
do{                                      	//循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
	temp = Remaining_len%128;           	 //剩余长度取余128
	Remaining_len = Remaining_len/128;  	 //剩余长度取整128
	if(Remaining_len>0)               	
		temp |= 0x80;                    	//按协议要求位7置位          
	Mqtt_send_data[Fixed_len] = temp;        //剩余长度字节记录一个数据
	Fixed_len++;	                     	//固定报头总长度+1    
}while(Remaining_len>0);                	 //如果Remaining_len>0的话,再次进入循环
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
可变报头

这里根据不同的报文类型而有不同,比如用QOS1的质量等级publish数据,那么它的可变报头包括:话题长度2个字节+话题若干字节+报文标识符2个字节,其它根据实际情况类似。
下面主要讲一下报文标识符
下表是列出来需要报文标识符的报文;
-包含报文标识符的控制报文
需要报文标识符的有:这些报文是 PUBLISH(QoS > 0时), PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE, SUBACK,UNSUBSCIBE,UNSUBACK。
客户端每次发送一个新的这些类型的报文 时都必须分配一个当前未使用的报文标识符 。如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报 文对应的确认后,这个报文标识符就释放可重用。
所以报文标识符是多少,可以自己定义,比如0x00 0x00到0xff 0xff都行,但是注意同一报文的报文标识符连续重发需要不一致,但是处理完以后,报文标识符又可以重用。
我下面的publish报文程序,直接用的固定的0x00 0x02,因为我上传以后会进行一个接收(QOS1上传),检测到接收后,我再传下一次,所以用了一个定值,至于其它的用法,可以更具自己需要编写规则。

有效载荷

有效载荷,通俗来说就是上传的数据,比如我上传数据我把数据组装为json格式,那么这一块就加在报文最后,这一部分就是有效载荷。
需要有效载荷的报文如下:
在这里插入图片描述

二、下面直接开整各个具体的报文(MQTT有个工程,可以拿来直接用,挺完善的,不过我觉得写的有点乱,所以我就把功能单独拎出来了,封装了自己的函数)

CONNECT – 连接服务端

报文格式:固定报头+剩余长度+可变报头(byte0–byte5协议名字,byte6协议级别, byte7连接标志(看下面讲解))+负载

其它没什么好说的,主要说一下 byte7连接标志
每一个位代表内容如下:
连接标志位
bit0:服务端必须验证CONNECT控制报文的保留标志位(第0位)是否为0,如果不为0必须断开客 户端连接 ;
bit1:清理会话 Clean Session(具体详细的介绍自己看文档,下面说的相对通俗一点,不准确的可以指出);
当MQTT客户端接入MQTT服务端时,选择是否继续之前的会话。如果不清理会话,MQTT服务端会在之前交互的基础上,继续交互如果清理会话,MQTT服务端必须新建一个全新的会话。
一般来说,客户端连接时总是将清理会话标志设置为0或1,并且不交替使用两种值。这 个选择取决于具体的应用。清理会话标志设置为1的客户端不会收到旧的应用消息,而且 在每次连接成功后都需要重新订阅任何相关的主题。清理会话标志设置为0的客户端会收 到所有在它连接断开期间发布的QoS 1和QoS 2级别的消息。因此,要确保不丢失连接断 开期间的消息,需要使用QoS 1或 QoS 2级别,同时将清理会话标志设置为0。
bit2:遗嘱标志 Will Flag
遗嘱标志(Will Flag)被设置为1,表示如果连接请求被接受了,遗嘱(Will Message)消息 必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个 遗嘱消息,除非服务端收到DISCONNECT报文时删除了这个遗嘱消息;
如果遗嘱标志被设置为1,连接标志中的Will QoS和Will Retain字段会被服务端用到,同时有 效载荷中必须包含Will Topic和Will Message字段;为0时反之;
如果遗嘱标志被设置为0,网络连接断开时,不能发送遗嘱消息。
遗嘱消息发布的条件,包括但不限于:
(1) 服务端检测到了一个I/O错误或者网络故障。
(2)客户端在保持连接(Keep Alive)的时间内未能通讯。
(3)客户端没有先发送DISCONNECT报文直接关闭了网络连接。
(4)由于协议错误服务端关闭了网络连接。

bit3 bit4:遗嘱QoS Will QoS
这两位用于指定发布遗嘱消息时使用的服务质量等级。 如果遗嘱标志被设置为0,遗嘱QoS也必须设置为0(0x00)。
如果遗嘱标志被设置为1,遗嘱QoS的值可以等于0(0x00),1(0x01),2(0x02)。它的值不能等 于3.

bit5:遗嘱保留 Will Retain
如果遗嘱消息被发布时需要保留,需要指定这一位的值。
如果遗嘱标志被设置为0,遗嘱保留(Will Retain)标志也必须设置为0。
如果遗嘱标志被设置为1:
(1)如果遗嘱保留被设置为0,服务端必须将遗嘱消息当作非保留消息发布。
(2)如果遗嘱保留被设置为1,服务端必须将遗嘱消息当作保留消息发布。

bit6:密码标志 Password Flag
如果密码(Password)标志被设置为0,有效载荷中不能包含密码字段;
如果密码(Password)标志被设置为1,有效载荷中必须包含密码字段;
如果用户名标志被设置为0,密码标志也必须设置为0。

bit7:用户名标志 User Name Flag
如果用户名(User Name)标志被设置为0,有效载荷中不能包含用户名字段 ;
如果用户名(User Name)标志被设置为1,有效载荷中必须包含用户名字段。

具体讲解请看代码中的注释:

//0x10 发送连接报文
void Mqtt_Send_0x10( MQTT_CMD_t *MQTT_Cmd )
{
	uint16_t len = 0;
	Fixed_len = 1;                   //连接报文中,固定报头长度暂时先=1
	Variable_len = 10;               //连接报文中,可变报头长度=10
	Payload_len = 2 + ClientID_len + 2 + Username_len + 2 + Passward_len; //连接报文中,负载长度      
	Remaining_len = Variable_len + Payload_len;                           //剩余长度=可变报头长度+负载长度
	
	//为何为0x01请参考上面固定报头的两个表格
	Mqtt_send_data[0]=0x10;                       //固定报头第1个字节 :固定0x01		
	//这里就用到了上面提到的剩余长度计算
	do{                                      //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
		temp = Remaining_len%128;            //剩余长度取余128
		Remaining_len = Remaining_len/128;   //剩余长度取整128
		if(Remaining_len>0)               	
			temp |= 0x80;                    //按协议要求位7置位          
		Mqtt_send_data[Fixed_len] = temp;         //剩余长度字节记录一个数据
		Fixed_len++;	                     //固定报头总长度+1    
	}while(Remaining_len>0);                 //如果Remaining_len>0的话,再次进入循环
	
	Mqtt_send_data[Fixed_len+0]=0x00;    //可变报头第1个字节 :固定0x00	            
	Mqtt_send_data[Fixed_len+1]=0x04;    //可变报头第2个字节 :固定0x04,代表协议4个字节
	Mqtt_send_data[Fixed_len+2]=0x4D;	//可变报头第3个字节 :固定0x4D, "M"
	Mqtt_send_data[Fixed_len+3]=0x51;	//可变报头第4个字节 :固定0x51, "Q"
	Mqtt_send_data[Fixed_len+4]=0x54;	//可变报头第5个字节 :固定0x54, "T"
	Mqtt_send_data[Fixed_len+5]=0x54;	//可变报头第6个字节 :固定0x54, "T"
	Mqtt_send_data[Fixed_len+6]=0x04;	//可变报头第7个字节 :固定0x04, 。对于3.1.1版协议,协议级别字段的值是 4(0x04)
	Mqtt_send_data[Fixed_len+7]=0xC2;	//可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱,安全等级为1, 不保留会话, 即1101 0010
	Mqtt_send_data[Fixed_len+8]=0x00; 	//可变报头第9个字节 :保活时间高字节 0x00
	Mqtt_send_data[Fixed_len+9]=0x64;	//可变报头第10个字节:保活时间高字节 0x64   100s
	
	/*     CLIENT_ID      */
	Mqtt_send_data[Fixed_len+10] = ClientID_len/256;                			  			    //客户端ID长度高字节
	Mqtt_send_data[Fixed_len+11] = ClientID_len%256;               			  			    //客户端ID长度低字节
	memcpy(&Mqtt_send_data[Fixed_len+12],ClientID,ClientID_len);                 			//复制过来客户端ID字串	
	/*     用户名        */
	Mqtt_send_data[Fixed_len+12+ClientID_len] = Username_len/256; 				  		    //用户名长度高字节
	Mqtt_send_data[Fixed_len+13+ClientID_len] = Username_len%256; 				 		    //用户名长度低字节
	memcpy(&Mqtt_send_data[Fixed_len+14+ClientID_len],Username,Username_len);                //复制过来用户名字串	
	/*      密码        */
	Mqtt_send_data[Fixed_len+14+ClientID_len+Username_len] = Passward_len/256;			    //密码长度高字节
	Mqtt_send_data[Fixed_len+15+ClientID_len+Username_len] = Passward_len%256;			    //密码长度低字节
	memcpy(&Mqtt_send_data[Fixed_len+16+ClientID_len+Username_len],Passward,Passward_len);   //复制过来密码字串

	len = Fixed_len + Variable_len + Payload_len;
	MQTT_Send_Data( Mqtt_send_data,len, &HN_Config.IP.Remote );//发送数据
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

CONNACK – 确认连接请求

格式:固定报头(0x20 0x02)+ 可变报头(连接确认标志+连接返回码) 无载荷
连接返回码
所以按照我们上面的配置返回码为:20 02 00 00

SUBSCRIBE - 订阅主题

格式:固定报头+报文标识符(订阅质量为QOS1)+话题(长度+内容)+订阅等级

//0x82  发送订阅报文
void Mqtt_Send_0x82( MQTT_CMD_t *MQTT_Cmd )
{
	uint16_t len = 0;
	Fixed_len = 2;                              //SUBSCRIBE报文中,固定报头长度=2
	Variable_len = 2;                           //SUBSCRIBE报文中,可变报头长度=2	
	Payload_len = 2 + strlen(S_TOPIC_NAME) + 1;   //计算有效负荷长度 = 2字节(topic_name长度)+ topic_name字符串的长度 + 1字节服务等级
	
	Mqtt_send_data[0]=0x82;                                    //第1个字节 :固定0x82                      
	Mqtt_send_data[1]=Variable_len + Payload_len;              //第2个字节 :可变报头+有效负荷的长度	
	Mqtt_send_data[2]=0x00;									//这里报文标识符固定为0x00 0x01
	Mqtt_send_data[3]=0x01;
	Mqtt_send_data[4]=strlen(S_TOPIC_NAME)/256;                  //第5个字节 :topic_name长度高字节
	Mqtt_send_data[5]=strlen(S_TOPIC_NAME)%256;		          //第6个字节 :topic_name长度低字节
	memcpy(&Mqtt_send_data[6],S_TOPIC_NAME,strlen(S_TOPIC_NAME));  //第7个字节开始 :复制过来topic_name字串		
	Mqtt_send_data[6+strlen(S_TOPIC_NAME)]=QoS1;                  //最后1个字节:订阅等级1 QoS1 = 0x01
	
	len = Fixed_len + Variable_len + Payload_len;
	MQTT_Send_Data( Mqtt_send_data,len, &HN_Config.IP.Remote );//发送数据
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

SUBACK – 订阅确认

格式:固定报头(0x90 0x03)+ 可变报头(报文标识符) + 载荷(成功与否状态)
载荷:
0x00 - 最大QoS 0
0x01 - 成功 – 最大QoS 1
0x02 - 成功 – 最大 QoS 2
0x80 - Failure 失败

所以按照上面的订阅请求,这里应该返回:90 03 00 01 01

PUBLISH – 发布消息

格式:固定报头+可变报头(话题+报文标识符)+载荷

固定报头
固定报头

Byte1 bit3:重发标志 DUP
如果DUP标志被设置为0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如 果DUP标志被设置为1,表示这可能是一个早前报文请求的重发。
客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1 [MQTT-3.3.1.-1].。 对于QoS 0的消息,DUP标志必须设置为0.
Byte1 bit2/bit1:服务质量等级 QoS
在这里插入图片描述

Byte1 bit0:保留标志 RETAIN
如果客户端发给服务端的PUBLISH报文的保留(RETAIN)标志被设置为1,服务端必须存储 这个应用消息和它的服务质量等级(QoS);
一个新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消 息,它必须被发送给这个订阅者;
如果服务端收到一条保留(RETAIN)标志 为1的QoS 0消息,它必须丢弃之前为那个主题保留的任何消息。它应该将这个新的QoS 0消 息当作那个主题的新保留消息,但是任何时候都可以选择丢弃它 — 如果这种情况发生了,那 个主题将没有保留消息;
服务端发送PUBLISH报文给客户端时,如果消息是作为客户端一个新订阅的结果发送,它必 须将报文的保留标志设为1 。当一个PUBLISH报文发送给客户端是因为匹配一 个已建立的订阅时,服务端必须将保留标志设为0,不管它收到的这个消息中保留标志的值是多少。
用途:对于发布者不定期发送状态消息这个场景,保留消息很有用。新的订阅者将会收到最近 的状态。(我这里没用到,如果用到后面再细纠)

注意:这里的json格式数据我是手动封装的,好像也有自动封装的json库,但是前辈说不好用,就没用到。而且这里功能单一,所以就手动封装了。

/*****************传感器参数*************************/
char s_time[]=    	"time";   		 //上报时间
char s_data[]=    	"data";   		 //data
char s_id[]=    	  "id";   		 //传感器ID
char s_sensors[]=    	  "sensors";   		 //传感器ID
char s_type[]=    	"type";   		 //传感器类型
char s_idx[]=    	  "idx";   		 //传感器索引
char s_status[]=    "status";   		 //传感器状态
char s_val[]=    	  "val";   		 //传感器数据


//0x44  上报传感器数据
void Mqtt_Send_0x44( MQTT_CMD_t *MQTT_Cmd )
{
	uint8_t i;
	uint16_t len = 0;
	uint16_t len1 = 0;
	uint8_t timer_temp[20];
	char json_temp[100] = {'\0'};
	char json_data[900] = {'\0'};
	
	//封装传感器数据
	BCDRtc_toAscll(timer_temp);
	sprintf(json_data,"{\"%s\":\"%s\",\"%s\":{\"%s\":%s,\"%s\":[", s_time, timer_temp, s_data, s_id, "0", s_sensors);
	len1 = strlen((char *)json_data);

	for( i = 0; i < HN_Config.Sensor.Sensor_485_Num; i++ )//添加485传感器数据 HN_Config.Sensor.Sensor_485_Num
	{
		sprintf(json_temp, 
		"{\"%s\":%u,\"%s\":%s,\"%s\":%u,\"%s\":%.2f,\"%s\":\"%s\"},",
		s_type, HN_Sensor_Data.sensor_data_485[i].type,
		s_idx, "0",
		s_status,sensor_data_485[i].state,
		s_val,sensor_data_485[i].Data.float_data,
		s_time,sensor_data_485[i].ascll_timer
		);
		strcat(json_data, json_temp);
	}
	
	for( i = 0; i < HN_Config.Sensor.Sensor_Other_Num; i++ )//添加非485传感器数据
	{
		sprintf(json_temp, 
		"{\"%s\":%u,\"%s\":%s,\"%s\":%u,\"%s\":%.2f,\"%s\":\"%s\"},",
		s_type, sensor_data_other[i].type,
		s_idx, "0",
		s_status,sensor_data_other[i].state,
		s_val,sensor_data_other[i].Data.float_data,
		s_time,sensor_data_other[i].ascll_timer
		);
		strcat(json_data, json_temp);
	}
	json_data[strlen(json_data ) - 1] = '\0';
	strcat(json_data,"]}}");
	
	Fixed_len = 1;                              //固定报头长度暂时先等于:1字节
	Variable_len = 4 + strlen(P_TOPIC_NAME);           //可变报头长度:2字节(topic长度)+ topic字符串的长度
	Payload_len = strlen((char *)json_data);                     //有效负荷长度:就是data_len
	Remaining_len = Variable_len + Payload_len; //剩余长度=可变报头长度+负载长度
	
	Mqtt_send_data[0]=0x32;                       //固定报头第1个字节 :固定0x32 qos1  	
	do{                                      //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
		temp = Remaining_len%128;            //剩余长度取余128
		Remaining_len = Remaining_len/128;   //剩余长度取整128
		if(Remaining_len>0)               	
			temp |= 0x80;                    //按协议要求位7置位          
		Mqtt_send_data[Fixed_len] = temp;         //剩余长度字节记录一个数据
		Fixed_len++;	                     //固定报头总长度+1    
	}while(Remaining_len>0);                 //如果Remaining_len>0的话,再次进入循环
	
	Mqtt_send_data[Fixed_len+0]=strlen(P_TOPIC_NAME)/256;
	Mqtt_send_data[Fixed_len+1]=strlen(P_TOPIC_NAME)%256;
	memcpy(&Mqtt_send_data[Fixed_len+2],P_TOPIC_NAME,strlen(P_TOPIC_NAME));

	Mqtt_send_data[Fixed_len+2+strlen(P_TOPIC_NAME)] = 0x00;  //报文标识符固定为0x00 0x02
	Mqtt_send_data[Fixed_len+3+strlen(P_TOPIC_NAME)] = 0x02;

	memcpy(&Mqtt_send_data[Fixed_len+4+strlen(P_TOPIC_NAME)],json_data,strlen((char *)json_data));
	
	len = Fixed_len + Variable_len + Payload_len;
	
	MQTT_Send_Data( Mqtt_send_data,len, &HN_Config.IP.Remote );//发送数据
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

注意
这里自己封装要注意格式正确,各个模块的括号那些一定要匹配,否则数据上传到平台将不会有显示。这里当时踩了一下午坑,要注意,这里两个在线工具可以使用,16进制转ascii码 和 json数据格式转化,用了验证上传的数据是否真确。
数据转化
json 格式转化

PUBACK –发布确认

上面的发布用的QOS1,所以需要发布确认,至于QOS2,我这里没有做,有需要可以自行测试。QOS0见上一篇文章,代码类似。

格式:固定报头(0x40 0x01) + 可变报头(报文标识符) 无载荷

所以上面的发布确认为: 0x40 0x01 0x00 0x02

PINGREQ – 心跳请求

格式:固定报头(0xC0) + 剩余长度(0x00)

//0xC0  发送心跳
void Mqtt_Send_0xC0( MQTT_CMD_t *MQTT_Cmd )
{
	Mqtt_send_data[0]=0xC0;              //第1个字节 :固定0xC0                      
	Mqtt_send_data[1]=0x00;              //第2个字节 :固定0x00 
	MQTT_Send_Data( Mqtt_send_data,2, &HN_Config.IP.Remote );//发送数据
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

PINGRESP – 心跳响应

格式:固定报头(0xD0) + 剩余长度(0x00)

返回:0xD0 0x00

DISCONNECT –断开连接

格式:固定报头(0xE0) + 剩余长度(0x00)

//0xe0  发送断链
void Mqtt_Send_0xE0( MQTT_CMD_t *MQTT_Cmd )
{
	Mqtt_send_data[0]=0xE0;              //第1个字节 :固定0xE0                      
	Mqtt_send_data[1]=0x00;              //第2个字节 :固定0x00 
	MQTT_Send_Data( Mqtt_send_data,2, &HN_Config.IP.Remote );//发送数据
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

三、mqtt.fx 和 wireshark的调试测试

mqtt.fx的使用这里就不做讲解了;

wireshark的使用可以参考这边博文,写的十分详细。
wireshar抓包分析

简单需要用的:
1、Packet Details Pane(数据包详细信息), 在数据包列表中选择指定数据包,在数据包详细信息中会显示数据包的所有详细信息内容。数据包详细信息面板是最重要的,用来查看协议中的每一个字段。各行信息分别为
(1)Frame: 物理层的数据帧概况
(2)Ethernet II: 数据链路层以太网帧头部信息
(3)Internet Protocol Version 4: 互联网层IP包头部信息
(4)Transmission Control Protocol: 传输层T的数据段头部信息,此处是TCP
(5)Hypertext Transfer Protocol: 应用层信息。
2、过滤查询
(1)协议过滤
比较简单,直接在抓包过滤框中直接输入协议名即可。
TCP,只显示TCP协议的数据包列表
HTTP,只查看HTTP协议的数据包列表
ICMP,只显示ICMP协议的数据包列表

注意:协议名称需要输入小写。

(2)IP过滤
host 192.168.1.104
src host 192.168.1.104
dst host 192.168.1.104

例:
ip.src == 192.168.1.104 显示源地址为192.168.1.104的数据包列表

ip.dst==192.168.1.104, 显示目标地址为192.168.1.104的数据包列表

ip.addr == 192.168.1.104 显示源IP地址或目标IP地址为192.168.1.104的数据包列表

(3)端口过滤
port 80
src port 80
dst port 80
例:
tcp.port ==80, 显示源主机或者目的主机端口为80的数据包列表。

tcp.srcport == 80, 只显示TCP协议的源主机端口为80的数据包列表。

tcp.dstport == 80,只显示TCP协议的目的主机端口为80的数据包列表。

(4)逻辑运算符&& 与、|| 或、!非
src host 192.168.1.104 && dst port 80 抓取主机地址为192.168.1.80、目的端口为80的数据包
host 192.168.1.104 || host 192.168.1.102 抓取主机为192.168.1.104或者192.168.1.102的数据包
!broadcast 不抓取广播数据包
例:
过滤多个条件组合时,使用and/or。比如获取IP地址为192.168.1.104的ICMP数据包表达式为ip.addr == 192.168.1.104 and icmp

其它的更多使用方法请参考博客或者自己搜索。

一共6个步骤,使用mqtt.fx 连接,订阅,上传,下发,心跳,断链,我们来看一下每一个过程的报文,

在这里插入图片描述

1.1连接服务器

可以看到从0x10固定报头开始,与上面所讲无异。
在这里插入图片描述

1.2服务器回复连接

可以看到从0x20固定报头开始,0x20 0x02 0x00 0x00 最后两个不管。
在这里插入图片描述

2.1发布消息

可以看到从0x32固定报头开始,0x83 0x06是长度 一直到0x00 0x01(报文标识符) 后面就是负载
在这里插入图片描述

2.2服务器回复收到

可以看到从0x40 0x02固定报头开始, 报文标识符0x00 0x01
在这里插入图片描述

3.1mqtt.fx订阅

可以看到从0x82固定报头开始, 报文标识符0x00 0x02 加后面的负载

在这里插入图片描述

3.2订阅回复

可以看到从0x90固定报头开始, 报文标识符0x00 0x02 加后面的负载 0x00
在这里插入图片描述

4.1服务器发布

可以看到从0x32固定报头开始, 后面是主题,再到报文标识符0x00 0x01(可以看到这里又用到0x00 0x01,说明前面订阅以释放) 加后面的负载
在这里插入图片描述

在这里插入图片描述

4.2回复服务器

可以看到从0x40固定报头开始,长度0x02 ,最后报文标识符 0x00 0x01 同发布一致
在这里插入图片描述

5.1发送心跳(0xC0 0x00)

在这里插入图片描述

5.2心跳回复(0xD0 0x00)

在这里插入图片描述

6断开连接(0xE0 0x00) 无回复

在这里插入图片描述

经过这一系列的操作,MQTT算是基本弄明白了,很开心,哈哈!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/218158
推荐阅读
相关标签
  

闽ICP备14008679号