目录
MQTT控制报文的类型 MQTT Control Packet type
剩余长度 Remaining Length(包括可变报头和负载的数据的长度)
前言
上一章我们用W5100S-EVB-PICO通过SNTP获取网络时间,那么本章我们介绍一下MQTT协议的内容以及使用开发板与MQTTX软件来实现mqtt通信。
如果您在阅读本章之后仍有不清楚的地方,可以私信联系我们或者评论区留言,我们会及时回复您的问题!
MQTT介绍
什么是mqtt?
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT主要控制报文
固定报头 Fixed header
每个MQTT控制报文都包含一个固定报头。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT控制报文的类型 | 用于指定控制报文类型的标志位 | ||||||
byte 2... | 剩余长度 |
MQTT控制报文的类型 MQTT Control Packet type
位置:第1个字节,二进制位7-4
名字 | 值 | 报文流动方向 | 描述 |
Reserved | 0 | 禁止 | 保留 |
CONNECT | 1 | 客户端到服务端 | 客户端请求连接服务端 |
CONNACK | 2 | 服务端到客户端 | 连接报文确认 |
PUBLISH | 3 | 两个方向都允许 | 发布消息 |
PUBACK | 4 | 两个方向都允许 | QoS 1消息发布收到确认 |
PUBREC | 5 | 两个方向都允许 | 发布收到(保证交付第一步) |
PUBREL | 6 | 两个方向都允许 | 发布释放(保证交付第二步) |
PUBCOMP | 7 | 两个方向都允许 | QoS 2消息发布完成(保证交互第三步) |
SUBSCRIBE | 8 | 客户端到服务端 | 客户端订阅请求 |
SUBACK | 9 | 服务端到客户端 | 订阅请求报文确认 |
UNSUBSCRIBE | 10 | 客户端到服务端 | 客户端取消订阅请求 |
UNSUBACK | 11 | 服务端到客户端 | 取消订阅报文确认 |
PINGREQ | 12 | 客户端到服务端 | 心跳请求 |
PINGRESP | 13 | 服务端到客户端 | 心跳响应 |
DISCONNECT | 14 | 客户端到服务端 | 客户端断开连接 |
Reserved | 15 | 禁止 | 保留 |
标志 Flags
固定报头第1个字节的剩余的4位 [3-0]包含每个MQTT控制报文类型特定的标志,如果收到非法的标志,接收者必须关闭网络连接。
控制报文 | 固定报头标志 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
CONNECT | Reserved | 0 | 0 | 0 | 0 |
CONNACK | Reserved | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP1 | QoS2 | QoS2 | RETAIN3 |
PUBACK | Reserved | 0 | 0 | 0 | 0 |
PUBREC | Reserved | 0 | 0 | 0 | 0 |
PUBREL | Reserved | 0 | 0 | 1 | 0 |
PUBCOMP | Reserved | 0 | 0 | 0 | 0 |
SUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
SUBACK | Reserved | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
UNSUBACK | Reserved | 0 | 0 | 0 | 0 |
PINGREQ | Reserved | 0 | 0 | 0 | 0 |
PINGRESP | Reserved | 0 | 0 | 0 | 0 |
DISCONNECT | Reserved | 0 | 0 | 0 | 0 |
- DUP1 =控制报文的重复分发标志
- QoS2 = PUBLISH报文的服务质量等级
- RETAIN3 = PUBLISH报文的保留标志
剩余长度 Remaining Length(包括可变报头和负载的数据的长度)
使用变成编码(1到4个字节表示,即最大可表示256M,每个字节可编码128个数值+1个延续位(最高位是延续位表示是否有更多字节,低7位表示128个数值)):
字节数 | 最小值 | 最大值 |
1 | 0 (0x00) | 127 (0x7F) |
2 | 128 (0x80, 0x01) | 16 383 (0xFF, 0x7F) |
3 | 16 384 (0x80, 0x80, 0x01) | 2 097 151 (0xFF, 0xFF, 0x7F) |
4 | 2 097 152 (0x80, 0x80, 0x80, 0x01) | 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F) |
可变报头 Variable header
可变报头的内容根据 控制报文类型 的不同而不同。(特别注意:部分控制报文需要 报文标识符字段)
控制报文 | 报文标识符字段 |
CONNECT | 不需要 |
CONNACK | 不需要 |
PUBLISH | 需要(如果QoS > 0) |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符,以此来标识是同一条信息如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。
当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。QoS 1的PUBLISH对应的是PUBACK,QoS 2的PUBLISH对应的是PUBCOMP
有效载荷 Payload
有效载荷是除控制报文格式以外的有效信息,CONNECT、PUBLISH、SUBSCRIBE等需要传递有效信息的协议帧都需要。
控制报文 | 有效载荷 |
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 不需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
协议格式讲解
MQTT报文格式参考文档:mqtt.v3.1.1
MQTT客户端连接服务器流程图
CONNECT报文
CONNECT 协议是客户端建立连接的第一个报文,通常都要带有鉴权的字段,一个CONNECT报文都会对应一个服务端的CONNACK报文。
得到CONNECT报文如下:
#16进制表示
102C00044D51545404C2003C000A636C69656E7469642F31000A757365726E616D652F31000870617373776F7264
#固定报头(剩余长度44个字节)
10 2C
#可变报头
00 04 4D 51 54 54 04 C2 00 3C
#余下为有效载荷字段
#客户端标识符(000A 表示长度为10个字节, 文本内容为 "clientid/1")
00 0A 63 6C 69 65 6E 74 69 64 2F 31
#遗嘱主题、遗嘱消息为启用,所以没有
#用户名(000A 表示长度为10个字节, 文本内容为 "username/1")
00 0A 75 73 65 72 6E 61 6D 65 2F 31
#密码(0008 表示长度为8个字节, 文本内容为 "password")
00 08 70 61 73 73 77 6F 72 64
可变报头非规范示例
PUBLISH报文
PUBLISH是发布消息协议报文,双向都可以使用。
得到PUBLISH报文如下:
#16进制表示
32100005746F70696300016D657373616765
#固定报头(qos1消息,非重传、非保留,剩余长度16个字节)
32 10
可变报头(5个字节的主题"topic",报文标识符为1,)
00 05 74 6F 70 69 63 00 01
#有效载荷("message")
6D 65 73 73 61 67 65
SUBSCRIBE报文
客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。
得到SUBSCRIBE报文如下:
#16进制表示
820A00010005746F70696300
#固定报头(剩余长度10个字节)
82 0A
#可变报头(报文标识符为1)
00 01
#有效载荷(长度为5个字节,topicfilter为 "topic", qos 为0)
00 05 74 6F 70 69 63 00
DISCONNECT报文
DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。
得到DISCONNECT报文如下:
#16进制表示
E0 00
硬件准备及接线方式
硬件准备
- W5100S-EVB-Pico开发板或/W5100SIO模块+树莓派RP2040
- 数据线、网线
- 路由器
- pc
接线方式(W5100SIO模块+树莓派RP2040)
- W5100SIO_MISO->RP2040_GPIO16
- W5100SIO_MOSI->RP2040_GPIO19
- W5100SIO_CS->RP2040_GPIO17
- W5100SIO_SCK->RP2040_GPIO18
- W5100SIO_RST->RP2040_GPIO20
测试
MQTT测试流程图
相关代码
我们打开例程中库文件的mqttx_client.c文件用到如下所示几个函数:mqtt_init是对mqtt的信息进行初始化,把配置信息填入;messageArrived函数主要作用是讲发布和订阅的信息进行判断打印,keep_alive函数是一个心跳包,如果超过设定值没有发送心跳包就进行mqtt_init函数初始化。
void mqtt_init(void)
{
int ret;
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
NewNetwork(&n, 1);
ConnectNetwork(&n, mqtt_params.server_ip, 1883);
MQTTClientInit(&c, &n, 1000, mqtt_send_buff, MQTT_SEND_BUFF_SIZE, mqtt_recv_buff, MQTT_RECV_BUFF_SIZE);
data.willFlag = 0;
data.MQTTVersion = 3;
data.clientID.cstring = mqtt_params.clientid;
data.username.cstring = mqtt_params.username;
data.password.cstring = mqtt_params.passwd;
data.keepAliveInterval = 30;
data.cleansession = 1;
connOK = MQTTConnect(&c, &data);
printf("Connected:%s\r\n", connOK == 0 ? "success" : "failed");
printf("Subscribing to %s\r\n", mqtt_params.subtopic1);
ret = MQTTSubscribe(&c, mqtt_params.subtopic1, mqtt_params.QOS, messageArrived);
printf("Subscribed:%s\r\n", ret == 0 ? "success" : "failed");
sleep_ms(300);
MQTTMessage pubmessage={
.qos=QOS2,
.dup=0,
.retained=0,
.id=0,
};
pubmessage.payload="hello mqtt!\r\n";
pubmessage.payloadlen=strlen(pubmessage.payload);
MQTTPublish(&c,mqtt_params.pubtopic,&pubmessage);
}
void messageArrived(MessageData* md)
{
unsigned char messagebuffer[512];
MQTTMessage* message = md->message;
if (mqtt_params.QOS)
{
memcpy(messagebuffer,(char*)message->payload,(int)message->payloadlen);
*(messagebuffer + (int)message->payloadlen + 1) = '\n';
printf("%s\r\n",messagebuffer);
}
if (mqtt_params.QOS)
printf("%.*s", (int)message->payloadlen, (char*)message->payload);
else
printf("%s%.*s%s%s", "RX:",(int)message->payloadlen, (char*)message->payload, mqtt_params.QOS,"\r\n");
}
void keep_alive(void)
{
if (!connOK)
{
if (MQTTYield(&c, 30))
{
mqtt_init();
}
}
}
网络配置信息和之前的一样,还多加了mqtt的连接参数,其中的参数主要包括服务器ip,端口号,客户端id,用户名和密码(由于是本地测试,可以不需要设置用户名和密码)然后就是发布和订阅消息(必须和服务器上的一致)然后是QOS等级设置。
#define SOCKET_ID 0
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)
#define MQTT_SEND_BUFF_SIZE 2048
#define MQTT_RECV_BUFF_SIZE 2048
uint8_t mqtt_send_buff[MQTT_SEND_BUFF_SIZE] = {0};
uint8_t mqtt_recv_buff[MQTT_RECV_BUFF_SIZE] = {0};
typedef struct MQTTCONNECTION
{
char mqttHostUrl[1024];
int port;
char clientid[1024];
char username[1024];
char passwd[1024];
uint8_t server_ip[4];
char pubtopic[255];
char subtopic1[255];
int QOS;
} mqttconn;
mqttconn mqtt_params = {
.server_ip = {54,244,173,190},
.port = 1883,
.clientid = "9a1d7719a8ac40d29311f26c5c5469dc",
.username = "mqtt_username",
.passwd = "123456",
.pubtopic = "1234",
.subtopic1 = "2345",
.QOS = 0,
};
unsigned char *data_ptr = NULL;
void network_init(void);
wiz_NetInfo net_info = {
.mac = {0x00, 0x08, 0xdc, 0x16, 0xed, 0x2e},
.ip = {192, 168, 124, 10},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 124, 1},
.dns = {8, 8, 8, 8},
.dhcp = NETINFO_STATIC};
MQTTClient c = {0};
Network n = {0};
int connOK;
bool repeating_timer_callback(struct repeating_timer *t);
void mqtt_init(void);
void messageArrived(MessageData *md);
void keep_alive(void);
wiz_NetInfo get_info;
static uint8_t ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {0,};
static uint8_t destip[4]={192, 168, 124, 1};
static uint16_t destport = 8080;
static uint16_t local_port =8000;
int main()
{
struct repeating_timer timer;
stdio_init_all();
sleep_ms(2000);
wizchip_initialize();
wizchip_setnetinfo(&net_info);
print_network_information(net_info);
add_repeating_timer_ms(1, repeating_timer_callback, NULL, &timer);
mqtt_init();
while(true)
{
// loopback_udpc(SOCKET_ID, ethernet_buf, destip, destport);
keep_alive();
sleep_ms(10);
}
}
bool repeating_timer_callback(struct repeating_timer *t)
{
MilliTimer_Handler();
return true;
}
打开MQTTX工具信息配置好mqtt的信息。
订阅和发布(对应代码上的订阅与发布信息)。
测试现象
我们可以看到串口打印信息中打印了网络连接上且配置好网络信息,最后连接上了mqttx上的mqtt公共服务器。
打开mqttx可以到由W5100S_EVB_PICO发来的“hello mqtt”,表示订阅成功。
用mqttx切换到发布消息,将信息发送给W5100S_EVB_PICO。
最后可以看到串口打印了mqttx下发的消息,表示发布成功。
相关链接:
本章例程链接:mqtt_cliten example
感谢大家的观看,对本例程有任何不清楚的地方或者想对产品有更多的了解可以私信或者在评论区留言,我们看到会及时回复您!下章为大家带来mqtt协议连接阿里云的讲解