16 MQTT协议(三)

16.3.4 PUBREC-发布收到

PUBREC 报文是对 QoS 等级 2 的 PUBLISH 报文的响应。它是 QoS 2 等级协议交换的第二个报文。

16.3.4.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(5) 保留位
0 1 0 1 0 0 0 0
Byte2 剩余长度
**剩余长度**

表示可变报头的长度。 对 PUBREC 报文它的值等于 2。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB
**有效载荷**

PUBREC 报文没有有效载荷。

16.3.4.2 PUBREC抓包报文

16.3.4.3 c语言构造pubrec报文

//如果是PUBREC报文,head_type=0x50
void mqtt_qos2_pubrec(int  sockfd , unsigned char *data,unsigned char head_type)
{
    uint16 msg_id = mqtt_parse_msg_id(data);/*报文标识符,回复报文和接受报文的标识符必须一样*/
	unsigned char qos2_pubrec_respon[]={head_type/*固定报头*/,0x02/*剩余长度*/, (msg_id&0xff00)>>8 , msg_id&0x00ff/*最后两个字节是报文标识符*/};
	send_msg(sockfd,qos2_pubrec_respon,sizeof(qos2_pubrec_respon));
}

16.3.5 PUBREL-发布释放

PUBREL 报文是对 PUBREC 报文的响应。 它是 QoS 2 等级协议交换的第三个报文。

16.3.5.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(6) 保留位
0 1 1 0 0 0 0 0
Byte2 剩余长度
**剩余长度**

表示可变报头的长度。 对 PUBREL 报文它的值等于 2。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB
**有效载荷**

PUBREL 报文没有有效载荷。

16.3.5.2 PUBREL抓包报文

16.3.5.3 c语言构造pubrel报文

//head_type=0x62
void mqtt_qos2_pubrel(int sockfd , unsigned char *data,unsigned char head_type)
{
    uint16 msg_id = mqtt_parse_msg_id(data);
	unsigned char qos2_pubrel_respon[]={head_type/*报文类型*/,0x02/*剩余长度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后两个字节是报文标识符*/};
	send_msg(sockfd,qos2_pubrel_respon,sizeof(qos2_pubrel_respon));
}

16.3.6 PUBCOMP-发布完成

PUBCOMP 报文是对 PUBREL 报文的响应。 它是 QoS 2 等级协议交换的第四个也是最后一个报文。

16.3.6.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(7) 保留位
0 1 1 1 0 0 0 0
Byte2 剩余长度
**剩余长度**

表示可变报头的长度。 对 PUBCOMP 报文它的值等于 2。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB
**有效载荷**

PUBCOMP 报文没有有效载荷。

16.3.6.2 PUBCOMP抓包报文

16.3.6.3 c语言构造pubcom报文

//head_type=0x70
void mqtt_qos2_pubcomp(int   sockfd , unsigned char *data,unsigned char head_type)
{
    uint16 msg_id = mqtt_parse_msg_id(data);/*报文标识符*/
	unsigned char qos2_pubcomp_respon[]={head_type/*报文类型*/,0x02/*剩余长度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后两个字节报文标识符*/};
	send_msg(sockfd,qos2_pubcomp_respon,sizeof(qos2_pubcomp_respon));
}

16.3.7 PINGREQ-心跳请求

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

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

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

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

16.3.7.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(12) 保留位
1 1 0 0 0 0 0 0
Byte2 剩余长度 0
**可变报头**

报文没有可变报头。

有效载荷

PINGREQ 报文没有有效载荷。

16.3.7.2 PINGREQ 抓包报文

16.3.7.3 c语言构造pingreq报文

int mqtt_ping(int sockfd)
{
	uint8 packet[] = {MQTT_MSG_PINGREQ/*报文类型*/,0x00/*剩余长度*/};
	int ret = send_msg(sockfd,packet, sizeof(packet));
	return ret;
}

16.3.8 PINGRESP – 心跳响应

服务端发送 PINGRESP 报文响应客户端的 PINGREQ 报文。 表示服务端还活着。

16.3.8.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(13) 保留位
1 1 0 1 0 0 0 0
Byte2 剩余长度 0
**可变报头**

报文没有可变报头。

有效载荷

PINGRESP 报文没有有效载荷。

16.3.8.2 PINGRESP 抓包报文

16.3.8.3 c语言构造pinpresp报文

void mqtt_ping_req_reply(int sockfd)
{
    uint8_t cmd[]={0xd0/*报文类型*/, 0x00/*剩余长度*/};
    send_msg(sockfd,cmd,sizeof(cmd));
}

16.3.9 DISCONNECT –断开连接

DISCONNECT 报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。

16.3.9.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(14) 保留位
1 1 1 0 0 0 0 0
Byte2 剩余长度 0

可变报头

DISCONNECT报文没有可变报头。

有效载荷

DISCONNECT 报文没有有效载荷。

16.3.9.2 DISCONNECT 抓包报文

16.3.9.3 c语言构造disconnect报文

int mqtt_disconnect(int sockfd) 
{
	uint8 packet[] = {MQTT_MSG_DISCONNECT/*报文类型*/,0x00/*剩余长度*/}; 
	int ret = client_send(sockfd,packet, sizeof(packet));
    DEBUG_INFO("ret=%d",ret);
	return ret;
}

16.3.10 SUBSCRIBE-订阅主题

客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。 每个订阅注册客户端关心的一个或多个主题。 为了将应用消息转发给与那些订阅匹配的主题, 服务端发送 PUBLISH 报文给客户端。 SUBSCRIBE报文也(为每个订阅)指定了最大的 QoS 等级, 服务端根据这个发送应用消息给客户端。

16.3.10.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(8) 保留位
1 1 1 0 0 0 0 0
Byte2 剩余长度 0

SUBSCRIBE 控制报固定报头的第 3,2,1,0 位是保留位, 必须分别设置为 0,0,1,0,服务端必须将其它的任何值都当做是不合法的并关闭网络连接。

剩余长度字段
等于可变报头的长度( 2 字节) 加上有效载荷的长度。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB

服务端收到客户端发送的一个 SUBSCRIBE 报文时, 必须使用 SUBACK 报文响应。SUBACK 报文必须和等待确认的 SUBSCRIBE 报文有相同的报文标识符。

有效载荷

7 6 5 4 3 2 1 0
主题
Byte1 主题长度MSB
Byte2 主题长度LSB
Byte3~n 主题
服务质量
保留 qos等级
ByteN+1 0 0 0 0 0 0 x x

QoS 不等于 0,1 或 2, 服务端必须认为 SUBSCRIBE 报文是不合法的并关闭网络连接。

16.3.10.2 SUBSCRIBE报文抓包

16.3.10.3 c语言构造subscribe报文

static uint16 su_seq = 1;
int mqtt_subscribe_theme(int sockfd,char *Theme , uint8_t Qos)
{
	su_seq++;//报文标识符
	if(su_seq == 0){
		su_seq = 1;
	}
	uint16_t MessageId = su_seq;
	uint8_t cmd[1024]={0};
    //报文标示符长度2 + 主题长度位占用2字节+主题内容+qos标识
	int data_length = 2+2+strlen(Theme)+1; 
	int playload_len = strlen(Theme);
	uint8_t len_byte[4] ={0x00 , 0x00 ,0x00 ,0x00};
	uint8_t byte_num = length_trans_byte_form(data_length , len_byte);/*把剩余长度转换成变长编码*/
	cmd[0] = 0x82;
	memcpy(&cmd[1] , len_byte , byte_num);

	cmd[1+byte_num]=(MessageId & 0xff00) >> 8 ;
	cmd[1+byte_num+1] = MessageId & 0x00ff;		
	cmd[1+byte_num+1+1] = (playload_len & 0xff00) >> 8;
	cmd[1+byte_num+1+1+1] = playload_len & 0x00ff;
	memcpy(&cmd[1+byte_num+1+1+1+1] , Theme , playload_len);
	cmd[1+byte_num+1+1+1+1+playload_len] = Qos;
	client_send(sockfd,cmd, 1+byte_num+1+1+1+1+playload_len+1);
}

16.3.11 SUBACK –订阅确认

服务端发送 SUBACK 报文给客户端,用于确认它已收到并且正在处理 SUBSCRIBE 报文。

16.3.11.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(9) 保留位
1 0 0 1 0 0 0 0
Byte2 剩余长度 0
**剩余长度字段** 等于可变报头的长度加上有效载荷的长度。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB

可变报头包含等待确认的 SUBSCRIBE 报文的报文标识符。

3.11.2 SUBACK抓包报文

16.3.11.3 c语言构造suback报文

void mqtt_subscribe_ack(int sockfd,const uint8 *buf)
{
    uint16 msg_id = mqtt_parse_msg_id(buf);/*提取报文标识符*/
    uint8 qos = MQTTParseMessageQos(buf);/*提取报文qos*/
    uint8 cmd[]={0x90,0x03/*剩余长度*/, (msg_id & 0xff00) >> 8, msg_id & 0x00ff,qos};
    send_msg(sockfd,cmd,sizeof(cmd));
}

16.3.12 UNSUBSCRIBE –取消订阅

客户端发送 UNSUBSCRIBE 报文给服务端, 用于取消订阅主题。

16.3.12.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(10) 保留位
1 0 1 0 0 0 0 0
Byte2 剩余长度 0

UNSUBSCRIBE 报文固定报头的第 3,2,1,0 位是保留位且必须分别设置为 0,0,1,0。 服务端必须认为任何其它的值都是不合法的并关闭网络连接。

剩余长度字段
等于可变报头的长度,加上有效载荷的长度。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB

可变报头包含一个报文标识符。

有效载荷

7 6 5 4 3 2 1 0
主题1
Byte1 主题长度MSB
Byte2 主题长度LSB
Byte3~n 主题
主题2

UNSUBSCRIBE 报文的有效载荷必须至少包含一个消息过滤器。 没有有效载荷的 UNSUBSCRIBE 报文是违反协议的。

16.3.12.2 UNSUBSCRIBE抓包报文

16.3.12.3 c语言构造unsubscribe报文

static uint16 un_seq = 1;
int mqtt_unsubscribe_theme(int sockfd,const char* topic)
{
	un_seq++;
	if(un_seq == 0){
		un_seq = 1;
	}
    uint16_t MessageId = un_seq;
	uint8_t cmd[1024]={0};
    //报文标示符长度2 + 主题长度位占用2字节+主题内容+qos标识
	int data_length = 2+2+strlen(topic)+1; 
	int playload_len = strlen(topic);
	uint8_t len_byte[4] ={0x00 , 0x00 ,0x00 ,0x00};
	uint8_t byte_num = length_trans_byte_form(data_length , len_byte);/*剩余长度转换成变长编码*/
	cmd[0] = 0xa2;
	memcpy(&cmd[1] , len_byte , byte_num);
    
	cmd[1+byte_num]=(MessageId & 0xff00) >> 8 ;
	cmd[1+byte_num+1] = MessageId & 0x00ff;		
	cmd[1+byte_num+1+1] = (playload_len & 0xff00) >> 8;
	cmd[1+byte_num+1+1+1] = playload_len & 0x00ff;
	memcpy(&cmd[1+byte_num+1+1+1+1] , topic , playload_len);
	client_send(sockfd,cmd,1+byte_num+1+1+1+1+playload_len+1);
	return 1;
}

16.3.13 UNSUBACK –取消订阅确认

服务端发送 UNSUBACK 报文给客户端用于确认收到 UNSUBSCRIBE 报文。

16.3.13.1 固定报头

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT报文类型(11) 保留位
1 0 1 1 0 0 0 0
Byte2 剩余长度 0
**剩余长度字段** 表示可变报头的长度, 对 UNSUBACK 报文这个值等于 2。

可变报头

bit 7 6 5 4 3 2 1 0
Byte1 报文标识符MSB
Byte2 报文标识符LSB

可变报头包含等待确认的 UNSUBSCRIBE 报文的报文标识符。

16.3.12.2 UNSUBSCRIBE ACK抓包报文

16.3.12.3 c语言构造unsubscribe报文

void mqtt_unsubscribe_ack(int sockfd,const uint8 *buf)
{
    uint16 msg_id = mqtt_parse_msg_id(buf);
    uint8 cmd[]={0xb0,0x02/*剩余长度*/,(msg_id & 0xff00) >> 8, msg_id & 0x00ff/*最后两个字节是报文标识符*/};
    send_msg(sockfd,cmd,sizeof(cmd));
}

16.3.14 服务端与客户端交互操作过程

16.3.14.1 编译

编译client之前先在代码中指定server ip

进入client目录,直接make即可

进入server目录,直接make即可

16.3.14.2 执行

先运行server
image

再运行client

Client操作流程

在server端查看