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 |
可变报头
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
再运行client
Client操作流程
在server端查看