使用静态全局数组保存MQTT报文,实现重传功能

时间: 2025-06-18 20:21:56 AIGC 浏览: 35
<think>好的,我现在需要解决用户的问题,即如何使用静态全局数组保存MQTT报文并实现重传功能。首先,我需要回忆MQTT协议的基本工作原理。MQTT是基于发布/订阅模型的轻量级协议,常用于物联网设备之间的通信。在不可靠的网络环境下,确保报文可靠传输是关键,所以重传机制很重要。 用户提到使用静态全局数组来保存报文。静态全局数组意味着在程序的整个生命周期内都存在,且对所有相关函数可见。这可能用于存储待确认的报文,以便在需要重传时能够快速访问。 接下来,我需要考虑如何设计这个数组。首先确定数组的结构,每个元素可能需要存储报文的数据、时间戳、重试次数等信息。例如,可以定义一个结构体,包含报文内容、报文ID(如Packet ID)、发送时间、重试次数等字段。 然后,需要考虑如何管理这个数组。例如,当发送一个报文时,将其添加到数组中,并启动定时器。如果收到确认(ACK),则从数组中移除该报文。如果超时未收到ACK,则触发重传,增加重试次数,并更新发送时间。同时,需要处理重试次数超过最大限制的情况,可能需要进行错误处理或丢弃报文。 另外,需要考虑数组的大小。静态数组的大小固定,因此需要合理估计最大未确认报文数量,避免溢出。或者,可以设计为环形缓冲区,循环利用数组空间,但这可能增加复杂性。 还需要考虑多线程或中断环境下的并发访问问题。如果系统中有多个任务或中断服务例程可能访问该数组,需要使用互斥锁或其他同步机制来保证数据的一致性。 然后,关于MQTT报文本身,需要明确哪些报文需要确认。例如,QoS 1的PUBLISH报文需要PUBACK,QoS 2需要更复杂的流程。因此,重传机制需要根据不同的QoS级别来设计,可能需要在数组中记录报文的QoS级别,以便正确处理。 接下来,实现步骤可能包括: 1. 定义静态全局数组和索引。 2. 实现报文发送函数,将报文存入数组并启动定时器。 3. 处理ACK响应,从数组中移除对应的报文。 4. 定时检查数组中是否有超时的报文,进行重传。 5. 管理重试次数,超过限制则处理为失败。 可能的挑战包括数组的管理效率,尤其是在频繁添加和删除的情况下。链表可能更高效,但用户明确要求使用数组,所以需要优化数组的查找和删除操作。例如,维护一个有效元素的计数,或使用标记法来标记已确认的报文,然后定期清理。 还需要注意内存的使用,静态全局数组会占用固定的内存空间,需要合理规划大小,避免资源浪费或不足。 最后,测试和验证是关键。需要模拟网络丢包的情况,确保重传机制按预期工作,并且数组管理正确,没有内存泄漏或越界访问的问题。</think>### 使用静态全局数组实现MQTT报文重传功能的分步解析 #### 1. **核心设计思路** - **静态全局数组**:在程序生命周期内固定存在,存储待确认的报文及元数据。 - **重传触发条件**:超时未收到ACK或收到NACK。 - **报文生命周期管理**:记录发送时间、重试次数、报文内容等关键信息。 #### 2. **数据结构定义** ```c #define MAX_PENDING_MESSAGES 10 // 最大未确认报文数 #define MAX_RETRY 3 // 最大重试次数 #define ACK_TIMEOUT_MS 5000 // 确认超时时间 typedef struct { uint16_t packet_id; // MQTT报文ID(QoS≥1时必需) uint8_t *payload; // 报文内容指针 uint32_t payload_len; // 报文长度 uint32_t send_timestamp; // 最近发送时间戳 uint8_t retry_count; // 当前重试次数 } MqttPendingMessage; static MqttPendingMessage pending_messages[MAX_PENDING_MESSAGES]; // 静态全局数组 static uint8_t msg_index = 0; // 当前写入位置索引 ``` #### 3. **关键功能实现步骤** **步骤1:发送报文时存入数组** ```c void mqtt_publish_with_retry(uint16_t packet_id, uint8_t *payload, uint32_t len) { // 检查数组是否已满 if (msg_index >= MAX_PENDING_MESSAGES) { // 处理溢出(如丢弃最旧报文或返回错误) return; } // 存储报文信息 pending_messages[msg_index] = (MqttPendingMessage){ .packet_id = packet_id, .payload = malloc(len), // 需动态分配内存 .payload_len = len, .send_timestamp = get_current_timestamp(), .retry_count = 0 }; memcpy(pending_messages[msg_index].payload, payload, len); // 发送报文 mqtt_send_packet(payload, len); msg_index = (msg_index + 1) % MAX_PENDING_MESSAGES; // 环形缓冲区写法 } ``` **步骤2:处理ACK响应** ```c void handle_mqtt_ack(uint16_t received_packet_id) { for (int i = 0; i < MAX_PENDING_MESSAGES; i++) { if (pending_messages[i].packet_id == received_packet_id) { free(pending_messages[i].payload); // 释放内存 memset(&pending_messages[i], 0, sizeof(MqttPendingMessage)); // 清空条目 return; } } } ``` **步骤3:定时检查重传** ```c void check_retry_timeout() { uint32_t current_time = get_current_timestamp(); for (int i = 0; i < MAX_PENDING_MESSAGES; i++) { if (pending_messages[i].payload != NULL && (current_time - pending_messages[i].send_timestamp) > ACK_TIMEOUT_MS) { if (pending_messages[i].retry_count < MAX_RETRY) { // 重传报文 mqtt_send_packet(pending_messages[i].payload, pending_messages[i].payload_len); pending_messages[i].send_timestamp = current_time; pending_messages[i].retry_count++; } else { // 超过最大重试次数,触发错误处理 handle_retry_failure(pending_messages[i].packet_id); free(pending_messages[i].payload); memset(&pending_messages[i], 0, sizeof(MqttPendingMessage)); } } } } ``` #### 4. **优化与注意事项** 1. **内存管理**: - 每次存储报文时需动态分配内存(`malloc`) - 收到ACK或重试失败后必须释放内存(`free`) - 建议添加NULL指针检查防止野指针 2. **线程安全**: ```c // 在多线程环境中添加互斥锁 static pthread_mutex_t array_mutex = PTHREAD_MUTEX_INITIALIZER; void mqtt_publish_with_retry(...) { pthread_mutex_lock(&array_mutex); // ... 操作数组 ... pthread_mutex_unlock(&array_mutex); } ``` 3. **报文去重**: - 使用唯一的`packet_id`标识每个报文 - 在QoS 1/2级别下,Broker会自动去重 - 客户端需实现Packet ID生成机制(通常为自增计数器) 4. **性能优化**: ```c // 使用标志位代替memset清空(节省CPU时间) typedef struct { // ... 原有字段 ... uint8_t is_valid; // 0=无效条目,1=有效条目 } MqttPendingMessage; // 查找时跳过无效条目 for (int i = 0; i < MAX_PENDING_MESSAGES; i++) { if (!pending_messages[i].is_valid) continue; // ... 处理有效条目 ... } ``` #### 5. **工作流程示意图** ```plaintext 发送报文 │ ˅ [存入静态数组] → [启动超时计时器] │ │ │ ├─超时未确认─→ [重传报文] → [重试计数+1] │ │ │ │ │ └─超过最大重试→ [丢弃报文并报错] │ │ └─收到ACK─→ [释放内存,清除数组条目] ``` #### 6. **实际应用建议** 1. **QoS级别选择**: - 仅对QoS 1/2报文实现重传(QoS 0无需确认) - QoS 2需要实现更复杂的PUBREC/PUBREL流程 2. **报文ID管理**: ```c static uint16_t next_packet_id = 1; // Packet ID生成器 uint16_t generate_packet_id() { uint16_t id = next_packet_id; next_packet_id = (next_packet_id == UINT16_MAX) ? 1 : next_packet_id + 1; return id; } ``` 3. **与现有MQTT库集成**: - 示例:在Paho MQTT回调中处理ACK ```c void messageDelivered(void* context, MQTTClient_deliveryToken dt) { // 根据deliveryToken(即packet_id)清除数组条目 handle_mqtt_ack((uint16_t)dt); } ``` 4. **测试方案**: - 使用网络模拟工具(如tc-netem)制造丢包 - 验证场景: - 正常ACK流程 - 超时重传(1次、N次) - 最大重试后错误处理 - 数组溢出处理 此方案通过静态数组实现轻量级重传机制,适合资源受限的嵌入式场景。对于高吞吐量系统,建议改用动态数据结构(如链表)+内存池优化。
阅读全文

相关推荐

void Aliyun_Iot(char *redata) { char qos, dup, retain; // 解析 ReceivelPublish 参数 int id; int TimeOut = 2000; static char IOT_state = 0; if (wifi_state == 0) // 1、 WIFI模块连接服务器 { WIFI_Clean(); if (0 == WIFI_Connect()) // WIFI模块连接服务器 { WIFI_Clean(); Aliyun_Init(); // 处理三元组信息,得到连接参数 wifi_state = 1; IOT_state = 1; return; } } if (IOT_state == 1) // 2、发送连接报文 { MQTT_Connect(clientid, // clientid username, // username password, // password WILL_TOPIC, // will_topic WILL_MESSAGE, // will_message USER_NAME_FLAG, // user_name_flag PASS_WORD_FLAG, // pass_word_flag WILL_RETAIN, // will_retain WILL_QOS, // will_qos WILL_FLAG, // will_flag CLEAN_SESSION, // clean_session KEEP_ALIVE); // keep_alive return; } if (IOT_state == 2) // 3、发送订阅报文 { MQTT_subscribe(*set_topics, topic_qos, SUBSCRIBE_ID, TOPIC_COUNT, 1); return; } /* * * 接收服务器下发的数据 * */ switch (redata[0] >> 4) // 首字节高4位表示报文类型 { case 2: if (MQTT_ConnAck(MQTT_RxBUFF) == 1) // 接收到订阅确认,发送订阅报文 IOT_state == 2; break; case 3: /* 接收到服务器下发 3X 报文 */ /* 解析报文中的 重发标志dup、QoS等级、保留标志retain、报文ID, 提取报文中的数据,并且实现硬件控制 */ if (0 == MQTT_ReceivelPublish(&dup, &qos, &retain, &id, redata)) { printf("\r\n\r\n* 解析 0x%02x 报文: dup = %d, qos = %d, retain = %d, id = %02X * \r\n", redata[0], dup, qos, retain, id); if (dup == 1) printf("\r\n* 收到一条重传报文 *\r\n"); /* 根据QoS等级响应 */ if (qos == 1) { printf("\r\n* 接收到 QoS1 等级报文, 需要回复 PubAck(40) 报文 *"); // 收到 Publish 报文立即回复 PubAck报文,(这里暂时不加入重传链表) // 收到 dup为1 的报文也直接回复 PubAck报文 MQTT_SendPubAck(id); } if (qos == 2) { printf("\r\n* 接收到 QoS2 等级报文, 需要回复 PubRec(50) 报文 *"); MQTT_SendPubRec(id); // 立即回复 PubRec(50) 报文,并加入重传链表 // 下一步,等待接收 (发布者发送 PUBREL(62) 释放控制报文) } } break; /*----------------------------------------------------------------------------------- * 使用 QoS 1 发布消息 * * QoS1 工作流程: * 步骤1 -> 单片机 发送 消息 PUBLISH -------------------- 发送 3X XX -- -- * 单片机重传 PUBLISH 报文,直到接收到 PUBACK(40) * * 步骤2 -> 服务器 回复 PUBACK(40) 发布确认 ------------- 收到 40 02 XX XX * 单片机移除重传链表中对应 ID 的QoS1等级 Publish报文 *-----------------------------------------------------------------------------------*/ case 4: /* 一直发送QoS1的Publish,直到得到 PubAck(40) 报文 */ id = MQTT_ReceivePubAck(redata); if (id != 0) // 得到 PubAck 报文ID,移除重传链表中对应 ID 的QoS1等级 Publish报文 { printf("\r\n* 移除重传链表中对应 ID 的QoS1等级 Publish报文 *"); remove_from_retry_list(id); // 移除重发链表对应 ID 的报文 } break; /*----------------------------------------------------------------------------------- * 使用 QoS 2 发布消息 * 确保消息可靠传输。适用于关键数据场景,但会增加网络开销。 * * QoS 工作流程: * 步骤1 -> 单片机 发送消息 PUBLISH -------------------- 发送 3X XX -- -- * 单片机[重传] PUBLISH 报文,直到接收到 PUBREC(50) * * 步骤2 -> 服务器 回复 PUBREC 发布收到 ---------------- 收到 50 02 XX XX * 单片机[移除]重传链表中对应 ID 的QoS2等级 Publish报文 * * 步骤3 -> 单片机 发送 PUBREL 释放控制 ---------------- 回复 62 02 XX XX * 单片机[重传] PUBREL 报文,直到接收到 PUBCOMP(70) * * 步骤4 -> 接收者 回复 PUBCOMP 完成传输 --------------- 收到 70 02 XX XX * 单片机[移除]重传链表中对应 ID 的PUBREL 报文, 完成通信 *-----------------------------------------------------------------------------------*/ case 5: // 步骤2->服务器 回复 PUBREC 发布收到-- -- -- -- -- -- -- --收到 50 02 XX XX // 单片机[移除] 重传链表中对应 ID 的QoS2等级 Publish报文 id = MQTT_ReceivePubRec(redata); if (id != 0) { remove_from_retry_list(id); MQTT_SendPubRel(id); // 需要回复 62 02 XX XX } break; case 6: // 步骤3->单片机 发送 PUBREL 释放控制-- -- -- -- -- -- -- --回复 62 02 XX XX // 单片机[重传] PUBREL 报文,直到接收到 PUBCOMP(70) id = MQTT_ReceivePubRel(redata); if (id != 0) { MQTT_SendPubComp(id); printf("\r\n* QoS2消息传输完成 *\r\n"); } break; case 7: // 步骤4->接收者 回复 PUBCOMP 完成传输-- -- -- -- -- -- -- -收到 70 02 XX XX // 单片机[移除] 重传链表中对应 ID 的PUBREL 报文,完成通信 id = MQTT_ReceivePubComp(redata); if (id != 0) { remove_from_retry_list(id); printf("\r\n* QoS2消息传输完成 *\r\n"); } break; case 9: if (MQTT_SubAck(redata) == 1) break; default: break; } }整理这段代码

void Aliyun_Init(void) { char tempbuff[256] = {0}; // 定义临时数组 // 客户端ID memset(clientid, 0, sizeof(clientid)); sprintf(clientid, "%s|securemode=3,signmethod=hmacsha1|", DEVICENAME); // 用户名 memset(username, 0, sizeof(username)); sprintf(username, "%s&%s", DEVICENAME, PRODUCTKEY); // 密码 memset(password, 0, sizeof(password)); sprintf(tempbuff, "clientId%sdeviceName%sproductKey%s", DEVICENAME, DEVICENAME, PRODUCTKEY); utils_hmac_sha1(tempbuff, strlen(tempbuff), password, DEVICESECRE, strlen(DEVICESECRE)); // 使用哈C加密 // 打印调试信息 printf("\r\n---- 初始化阿里云连接参数 ----\r\n"); printf("\r\n---- 构建客户端ID ----\r\n"); mqtt_debug(clientid, strlen(clientid)); printf("\r\n---- 构建用户名 ----\r\n"); mqtt_debug(username, strlen(username)); printf("\r\n---- 构建密码 ----\r\n"); mqtt_debug(password, strlen(password)); printf("\r\n---- 服务器IP ----\r\n%s:1883\r\n\r\n", PRODUCTKEY ".iot-as-mqtt.cn-shanghai.aliyuncs.com"); // 打印调试信息 服务器IP } char Aliyun_Iot(char *redata, char *return_buff) { char clientid[CLIENTID_LEN]; // 存放客户端ID的缓冲区 char username[USERNAME_LEN]; // 存放用户名的缓冲区 char password[PASSWORD_LEN]; // 存放密码的缓冲区 // MQTT报文解析相关变量 char qos, dup, retain; // QoS级别、重复标志、保留标志 int id; // 报文标识符(Packet Identifier) int temp; // 临时存储报文类型(高4位) static IotState state = STATE_MQTT_CONNECT; // 状态机当前状态 printf("\n[Aliyun_Iot]连接 WIFI \n"); Aliyun_Init(); // 初始化WiFi模块,在定时器2保活机制中实现WiFi重连 if (WIFI_Connect() == 0 && wifi_state == 0) { wifi_state = 1; state = STATE_MQTT_CONNECT; printf("\n[Aliyun_Iot]WIFI 连接成功 \n"); } // ================= 主状态机处理 ================= switch (state) { // ---------- MQTT连接阶段 ---------- case STATE_MQTT_CONNECT: if (0 == MQTT_Connect(clientid, username, password, WILL_TOPIC, WILL_MESSAGE, USER_NAME_FLAG, PASS_WORD_FLAG, WILL_RETAIN, WILL_QOS, WILL_FLAG, CLEAN_SESSION, KEEP_ALIVE)) { state = STATE_RUNNING; printf("\n[Aliyun_Iot]连接报文发送成功,等待连接确认 \n"); } break; void Aliyun_Init(void)函数的调用对吗,可以把生成的数据保存到对应数组吗

/* 1、客户端请求与服务端建立连接 C -> S */ void MQTT_Connect(void) { char data[256] = {0}; // 报文缓存数组 int len = 0; // 报文长度计数变量 int Data_Len = 0; // 剩余长度(可变报头 + 有效载荷) char flags = 0; // 连接标志 //------------------ 连接标志配置 ----------------------- // bit1 是否清除会话 if (CLEAN_SESSION) flags |= 0x02; // bit2 是否使用遗嘱消息 if (WILL_FLAG) { flags |= 0x04; // bit3、bit4 遗嘱QoS等级, 默认 QoS0 if (1 == WILL_QOS) // QoS1 flags |= 0x08; if (2 == WILL_QOS) // QoS2 flags |= 0x10; // bit5 是否保留本次会话 if (WILL_RETAIN) flags |= 0x20; } // bit6、bit7 是否使用账号、密码 if (USER_NAME_FLAG ) flags |= 0xC0; // 0x80 | 0x40 //------------------ 连接标志配置 END ----------------------- // 剩余长度 = 可变报头 + 有效载荷(每个字段包含两个字节的长度标识) Data_Len = 10 + (2 + strlen(ClientID)); if (WILL_FLAG) Data_Len += (2 + strlen(WILL_TOPIC)) + (2 + strlen(WILL_MESSAGE)); // 当使用遗嘱消息 if (USER_NAME_FLAG ) Data_Len += (2 + strlen(UserName)) + (2 + strlen(PassWard)); // 当使用用户名和密码登录 // 1. Connect固定报头(Fixed header) // 1.1 控制报文类型 data[len++] = 0x10; // MQTT Message Type CONNECT // 1.2 剩余长度(不包括固定头部) do { char encodedByte = Data_Len % 128; Data_Len = Data_Len / 128; if (Data_Len > 0) // if there are more data to encode, set the top bit of this byte encodedByte = encodedByte | 128; data[len++] = encodedByte; // 填充剩余长度的值 } while (Data_Len > 0); // 2. 可变报头(Variable header) data[len++] = 0; // 2.1 协议名(Protocol Name) data[len++] = 4; data[len++] = 'M'; data[len++] = 'Q'; data[len++] = 'T'; data[len++] = 'T'; data[len++] = 4; // 2.2 协议级别(3.1.1 版协议) data[len++] = flags; // 2.3 连接标志(Connect Flags) C2:置位用户名标志,密码标志,清理会话标志 data[len++] = KEEP_ALIVE / 256; // 2.4 保持连接(Keep Alive) data[len++] = KEEP_ALIVE % 256; // 3. 有效载荷(Payload) /* 3.1 客户端ID */ data[len++] = strlen(ClientID) / 256; data[len++] = strlen(ClientID) % 256; memcpy(&data[len], ClientID, strlen(ClientID)); len += strlen(ClientID); if (WILL_FLAG) { /* 3.2 遗嘱主题 */ data[len++] = strlen(WILL_TOPIC) / 256; data[len++] = strlen(WILL_TOPIC) % 256; memcpy(&data[len], WILL_TOPIC, strlen(WILL_TOPIC)); len += strlen(WILL_TOPIC); /* 3.3 遗嘱消息 */ data[len++] = strlen(WILL_MESSAGE) / 256; data[len++] = strlen(WILL_MESSAGE) % 256; memcpy(&data[len], WILL_MESSAGE, strlen(WILL_MESSAGE)); len += strlen(WILL_MESSAGE); } if (USER_NAME_FLAG) { /* 3.4 用户名 */ data[len++] = strlen(UserName) / 256; data[len++] = strlen(UserName) % 256; memcpy(&data[len], UserName, strlen(UserName)); len += strlen(UserName); /* 3.5 密码 */ data[len++] = strlen(PassWard) / 256; data[len++] = strlen(PassWard) % 256; memcpy(&data[len], PassWard, strlen(PassWard)); len += strlen(PassWard); } // MQTT_SendData(data, len); // 通过串口发送数据到服务器 Write_MQTT_TxDataBuff(data, len); // 将数据复制到发送缓冲数组 #if MQTT_DEBUG // 串口显示信息 printf("\r\n* 1、CONNECT – 连接服务端 *: \r\n"); mqtt_debug(data, len); // 打印调试信息 #endif WIFI_Clean(); } 优化这段代码,实现重传机制

/* MQTT解析器测试函数 */ void mqtt_parser_test(void) { uint8_t packet_buffer[256]; uint32_t result; uint8_t test_data[64]; uint8_t data[64]; uint16_t i; uint8_t passed = 1; /* 初始化队列 */ Queue_Init(&q); printf("\r\n===== MQTT解析器测试开始 =====\r\n"); /* 测试用例1: 有效PUBLISH报文 (QoS0) */ { data[] = { 0x30, 0x0A, /* PUBLISH QoS0 + 剩余长度 */ 0x00, 0x03, 'a', 'b', 'c', /* 主题 */ 0x00, 0x00, /* 报文标识符 (QoS0时忽略) */ 'h', 'e', 'l', 'l', 'o' /* 负载 */ }; printf("\r\n测试用例1: 有效PUBLISH报文 (QoS0)\r\n"); Queue_Writes(&q, data, sizeof(data)); result = mqtt_parser(&q, packet_buffer); if (result == sizeof(data)) { printf(" - 测试通过: 成功解析报文,长度=%lu\r\n", result); } else { printf(" - 测试失败: 预期长度=%lu, 实际长度=%lu\r\n", (uint32_t)sizeof(data), result); passed = 0; } } /* 测试用例2: 无效报文类型 */ { data[] = {0x38, 0x00}; /* 无效的PUBLISH */ printf("\r\n测试用例2: 无效报文类型\r\n"); Queue_Writes(&q, data, sizeof(data)); result = mqtt_parser(&q, packet_buffer); if (result == 0) { printf(" - 测试通过: 正确拒绝无效报文\r\n"); } else { printf(" - 测试失败: 预期0, 实际%lu\r\n", result); passed = 0; } } /* 测试用例3: 剩余长度格式错误 */ { data[] = { 0x30, /* PUBLISH QoS0 */ 0xFF, 0xFF, 0xFF, 0xFF, 0x80 /* 非法的剩余长度格式 */ }; printf("\r\n测试用例3: 剩余长度格式错误\r\n"); Queue_Writes(&q, data, sizeof(data)); result = mqtt_parser(&q, packet_buffer); if (result == 0) { printf(" - 测试通过: 正确检测剩余长度错误\r\n"); } else { printf(" - 测试失败: 预期0, 实际%lu\r\n", result); passed = 0; } } /* 测试用例4: 报文不完整 */ { data[] = { 0x30, 0x05, /* PUBLISH QoS0 + 剩余长度 */ 0x00, 0x03, 'a', 'b' /* 缺少一个字节 */ }; printf("\r\n测试用例4: 报文不完整\r\n"); Queue_Writes(&q, data, sizeof(data)); result = mqtt_parser(&q, packet_buffer); if (result == 0) { printf(" - 测试通过: 正确处理不完整报文\r\n"); } else { printf(" - 测试失败: 预期0, 实际%lu\r\n", result); passed = 0; } } /* 测试用例5: 超时处理 */ { data[] = {0x30, 0x80}; /* 不完整的剩余长度 */ printf("\r\n测试用例5: 超时处理\r\n"); Queue_Writes(&q, data, sizeof(data)); /* 模拟多次调用以触发超时 */ for (i = 0; i <= MQTT_PARSE_TIMEOUT; i++) { result = mqtt_parser(&q, packet_buffer); } if (result == 0 && Queue_Length(&q) == 0) { printf(" - 测试通过: 正确处理超时并丢弃数据\r\n"); } else { printf(" - 测试失败: 队列长度=%u, 结果=%lu\r\n", Queue_Length(&q), result); passed = 0; } } /* 测试用例6: 多个报文连续解析 */ { data[] = { /* 报文1: CONNECT */ 0x10, 0x10, /* 固定报头 */ 0x00, 0x04, 'M', 'Q', 'T', 'T', /* 协议名 */ 0x04, /* 协议级别 */ 0x02, /* 连接标志 */ 0x00, 0x0A, /* 保持活动 */ 0x00, 0x03, 'c', 'i', 'd', /* 客户端ID */ /* 报文2: PUBLISH QoS1 */ 0x32, 0x0A, /* PUBLISH QoS1 */ 0x00, 0x03, 't', 'o', 'p', /* 主题 */ 0x00, 0x01, /* 报文标识符 */ 'm', 's', 'g' /* 负载 */ }; printf("\r\n测试用例6: 多个报文连续解析\r\n"); Queue_Writes(&q, data, sizeof(data)); /* 解析第一个报文 */ result = mqtt_parser(&q, packet_buffer); if (result == 16) { printf(" - 报文1解析成功: 长度=%lu\r\n", result); /* 解析第二个报文 */ result = mqtt_parser(&q, packet_buffer); if (result == 12) { printf(" - 报文2解析成功: 长度=%lu\r\n", result); printf(" - 测试通过: 成功解析多个报文\r\n"); } else { printf(" - 测试失败: 报文2预期12, 实际%lu\r\n", result); passed = 0; } } else { printf(" - 测试失败: 报文1预期16, 实际%lu\r\n", result); passed = 0; } } /* 测试用例7: 有效CONNECT报文 */ { data[] = { 0x10, 0x13, /* CONNECT + 剩余长度 */ 0x00, 0x04, 'M', 'Q', 'T', 'T', /* 协议名 */ 0x04, /* 协议级别 */ 0x02, /* 连接标志 */ 0x00, 0x0A, /* 保持活动 */ 0x00, 0x05, 'c', 'l', 'i', 'e', 'n', /* 客户端ID */ 0x00, 0x04, 'u', 's', 'e', 'r', /* 用户名 */ 0x00, 0x04, 'p', 'a', 's', 's' /* 密码 */ }; printf("\r\n测试用例7: 有效CONNECT报文\r\n"); Queue_Writes(&q, data, sizeof(data)); result = mqtt_parser(&q, packet_buffer); if (result == sizeof(data)) { printf(" - 测试通过: 成功解析CONNECT报文\r\n"); } else { printf(" - 测试失败: 预期长度=%lu, 实际长度=%lu\r\n", (uint32_t)sizeof(data), result); passed = 0; } } /* 最终测试结果 */ printf("\r\n===== 测试结果 =====\r\n"); if (passed) { printf("所有测试用例通过!\r\n"); } else { printf("部分测试用例失败!\r\n"); } printf("====================\r\n"); } 修改为支持stm32平台,标准库,C89标准

/* 3、PUBLISH 发布消息 C <=> S */ char MQTT_Publish(int qos, int retain, char *post_topic, unsigned int id, char *message) { const int BUFF_SIZE = 512; char data[BUFF_SIZE] = {0}; int len = 0; int remaining_length; int topic_length; int message_length; int encoded_len; int temp_remaining_length; int encoded_remaining_len_bytes = 0; char remaining_encoded[4]; topic_length = strlen(post_topic); message_length = strlen(message); /* ------------------------------- 报文检查、配置 ------------------------------------ */ if (qos < 0 || qos > 2) // qos值不在0-2范围内 return 1; if (topic_length == 0 || topic_length > 65535) // 主题长度小于65535 return 2; if (qos > 0 && id == 0) // QoS>0时必须提供有效id return 3; if (!message) // 消息内容不可为空 return 4; /* 剩余长度计算 */ remaining_length = (2 + topic_length) + // 主题名(2字节长度+内容) ((qos > 0) ? 2 : 0) + // QoS>0时需要报文标识符(2字节) message_length; // 消息内容长度 /* 缓冲区容量检查 */ temp_remaining_length = remaining_length; do { encoded_remaining_len_bytes++; temp_remaining_length /= 128; } while (temp_remaining_length > 0); /* 检查总长度是否超过BUFF_SIZE(512字节) */ if ((1 + encoded_remaining_len_bytes + remaining_length) > BUFF_SIZE) return 5; /* ------------------------------- 报文检查、配置结束 ------------------------------------ */ /* ------------------------------- 开始构建报文 ------------------------------------ */ /* 固定报头构造 */ data[len++] = 0x30 | (qos << 1) | retain; /* 剩余长度编码 */ encoded_len = encode_remaining_length(remaining_length, remaining_encoded); memcpy(&data[len], remaining_encoded, encoded_len); len += encoded_len; /* 可变报头与有效载荷 */ // 主题名 data[len++] = (topic_length >> 8) & 0xFF; data[len++] = topic_length & 0xFF; memcpy(&data[len], post_topic, topic_length); len += topic_length; /* 报文标识符(QoS > 0) */ if (qos > 0) { data[len++] = (id >> 8) & 0xFF; data[len++] = id & 0xFF; id++; } /* 有效载荷 */ memcpy(&data[len], message, message_length); len += message_length; /* ------------------------------- 构建报文完成 ------------------------------------ */ /* 数据发送 */ MQTT_SendData(data, len); // 直接发送数据 mqtt_debug(data, len, "\r\n* 3、PUBLISH – 发布消息 *\r\n"); if (qos > 0) { data[0] |= 0x08; // 置位dup重发标志 if (add_to_retry_list(data, len, id) != 0) // 将报文添加到重发链表 return 5; // 内存申请失败 mqtt_debug(data, len, "\r\n* 将报文添加到重发链表 *\r\n"); } WIFI_Clean(); return 0; // 报文构造并发送成功 } void print_packet_info(unsigned int message_id) { PublishPacket *current = packet_list; // 从链表头开始遍历 int found = 0; // 查找标记 int i; while (current != NULL) { if (current->message_id == message_id) { // 打印报文详细信息 printf("==== 找到报文 ID: %u ====\n", message_id); printf("数据指针地址: %p\n", (void*)current->data); printf("数据长度: %d bytes\n", current->len); printf("剩余重试次数: %d\n", current->retries); // 打印原始数据内容(十六进制格式) printf("数据内容[HEX]: "); for (i = 0; i < current->len; ++i) { printf("%02X ", (unsigned char)current->data[i]); } printf("\n============================\n"); found = 1; // 标记已找到 } current = current->next; // 移动到下一个节点 } if (!found) printf("警告: 未找到报文 ID 为 %u 的节点!! \n", message_id); } 这2个函数读取的ID有偏差

/* 3、PUBLISH 发布消息 C <=> S */ void MQTT_Publish(char dup, char qos, char retain, char *post_topic, char *message) { char data[256] = {0}; int len = 0; static int id = 0; int Data_Len = 0; int topic_len = strlen(post_topic); int message_len = strlen(message); Data_Len = (2 + topic_len) + (qos ? 2 : 0) + message_len; // 1、固定报头 if(dup == 1) // 重发标志 data[len] |= 0x08; if(qos == 1) // 消息质量等级 data[len] |= 0x02; else if(qos == 2) data[len] |= 0x04; if(retain == 1) // 保留标志 data[len] |= 0x01; data[len++] |= 0x30; // 1.2剩余长度(不包括固定头部) do { char encodedByte = Data_Len % 128; Data_Len = Data_Len / 128; // if there are more data to encode, set the top bit of this byte if (Data_Len > 0) encodedByte = encodedByte | 128; data[len++] = encodedByte; } while (Data_Len > 0); // 2、可变报头 // 2.1、主题名 data[len++] = topic_len / 256; // topic长度高字节 data[len++] = topic_len % 256; // topic长度低字节 memcpy(&data[len], post_topic, topic_len); // 拷贝topic字符串 len += topic_len; // 2.2、报文标识符(当Qos大于0时) if (qos) { data[len++] = MQTT_PUBLISH_ID / 256; data[len++] = MQTT_PUBLISH_ID % 256; data[len++] = id / 256; data[len++] = id % 256; id++; } // 3、有效载荷 memcpy(&data[len], message, message_len); // 有效负荷:拷贝data数据 len += message_len; // MQTT_SendData(data, len); // 通过串口发送数据到服务器 Write_MQTT_TxDataBuff(data, len); #if MQTT_DEBUG // 串口显示信息 printf("\r\n* 3、PUBLISH – 发布消息 *: \r\n"); mqtt_debug(data, len); // 打印调试信息 #endif WIFI_Clean(); }

/* 3、PUBLISH 发布消息 C <=> S */ char MQTT_Publish(int dup, int qos, int retain, char *post_topic, unsigned int id, char *message) { const int BUFF_SIZE = 512; char data[BUFF_SIZE] = {0}; int len = 0; int remaining_length; int encoded_remaining_len_bytes = 0; int temp_remaining_length; int topic_length = post_topic ? strlen(post_topic) : 0; int message_length = message ? strlen(message) : 0; if (topic_length == 0 || topic_length > 65535) // 主题长度小于65535 return 1; if (!message) // 消息内容不可为空 return 2; if (qos > 0 && id == 0) // QoS>0时必须提供有效id return 3; if (qos == 0 && dup != 0) // QoS为0,不支持重发 return 4; if (qos < 0 || qos > 2) return 5; /* 剩余长度计算 */ remaining_length = (2 + topic_length) + ((qos > 0) ? 2 : 0) + message_length; /* 缓冲区容量检查 */ temp_remaining_length = remaining_length; do { encoded_remaining_len_bytes++; temp_remaining_length /= 128; } while (temp_remaining_length > 0); /* 确保数据长度不超过缓存大小 */ if ((1 + encoded_remaining_len_bytes + remaining_length) > BUFF_SIZE) { return 6; } /* 固定报头构造 */ data[len++] = 0x30 | (dup << 3) | (qos << 1) | retain; /* 剩余长度编码 */ char remaining_encoded[4]; int encoded_len = encode_remaining_length(remaining_length, remaining_encoded); memcpy(&data[len], remaining_encoded, encoded_len); len += encoded_len; /* 可变报头与有效载荷 */ // 主题名 data[len++] = (topic_length >> 8) & 0xFF; data[len++] = topic_length & 0xFF; memcpy(&data[len], post_topic, topic_length); len += topic_length; /* 报文标识符(QoS > 0) */ if (qos > 0) { data[len++] = (id >> 8) & 0xFF; data[len++] = id & 0xFF; } /* 有效载荷 */ memcpy(&data[len], message, message_length); len += message_length; /* 数据发送 */ Write_MQTT_TxDataBuff(data, len); mqtt_debug(data, len, "\r\n* 3、PUBLISH – 发布消息 *\r\n"); WIFI_Clean(); return 0; } 完善这个代码,实现QOS1等级重发机制,在定时器中断中调用,给出完整代码

最新推荐

recommend-type

2022年单片机-第讲.ppt

2022年单片机-第讲.ppt
recommend-type

protobuf-lite-3.5.0-13.el8.tar.gz

# 适用操作系统:Centos8 #Step1、解压 tar -zxvf xxx.el8.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm
recommend-type

2020年大学生网络公司实习报告范文.doc

2020年大学生网络公司实习报告范文.doc
recommend-type

计算机工作总结范文.doc

计算机工作总结范文.doc
recommend-type

SSRSSubscriptionManager工具:简化SSRS订阅的XML文件导入

### 知识点概述 #### 标题知识点 1. **SSRSSubscriptionManager**: 这是一个专门用于管理SQL Server Reporting Services (SSRS) 订阅的工具或脚本。它允许用户从一个集中的位置管理SSRS订阅。 2. **从XML文件导入SSRS订阅**: 描述了一个通过读取XML文件来配置SSRS订阅的过程。这可能是为了减少重复的手动设置和避免错误,提高管理效率。 #### 描述知识点 3. **快速部署多个SSRS订阅**: 该工具或脚本的一个主要功能是能够快速设置多个订阅,这比传统的SSRS在线向导更为高效。 4. **标准SSRS在线向导的局限性**: 描述了标准SSRS向导的不足之处,例如操作缓慢、单次只能设置一个订阅,以及易于出现人为错误。 5. **SSRS订阅管理器的优势**: 解释了为什么使用SSRS订阅管理器比标准向导更可靠。它允许使用预定义的XML文档进行设置,这些文档可以经过测试和验证以减少错误。 6. **受控文档**: 强调了使用SSRS订阅管理器的一个好处是能够控制订阅设置,使其更为可靠且易于管理。 7. **版本控制和订阅设置**: 讨论了SSRS报告可以进行版本控制,但是传统的订阅设置通常不包含在版本控制中,而SSRS订阅管理器提供了一种方式,可以对这些设置进行记录和控制。 #### 标签知识点 8. **C#**: 指示了实现SSRSSubscriptionManager可能使用的技术,C# 是一种面向对象的编程语言,通常用于开发.NET应用程序,包括SSRS订阅管理器。 #### 压缩包子文件名列表 9. **SSRSSubscriptionManager-master**: 表示这是一个开源项目或组件的主干文件夹。名称表明这是一个版本控制仓库中的主分支,可能包含了源代码、项目文件和其他资源文件。 ### 详细知识点 #### 关于SSRS - SQL Server Reporting Services (SSRS) 是一个服务器基础的报告平台,它能够通过Web界面、文件共享和电子邮件来交付报表内容。SSRS用户可以根据数据源生成数据驱动的报表,并设置订阅以便自动分发这些报表。 - SSRS订阅是一个功能,允许用户根据设定的计划或用户触发条件自动获取报表。订阅可以是快照订阅、数据驱动订阅或基于事件的订阅。 #### 关于SSRSSubscriptionManager - SSRSSubscriptionManager是一个工具,其设计意图是简化SSRS订阅的管理过程。它允许管理员在单个操作中部署大量订阅,相比于传统方法,它极大地节省了时间。 - 通过使用XML文件来定义订阅的设置,该工具提供了更高的准确性和一致性,因为XML文件可以被严格地测试和审核。 - 自动化和批量操作可以减少因手动设置造成的错误,并且提高了操作效率。这对于有大量报表和订阅需求的企业来说尤为重要。 - SSRSSubscriptionManager的出现也表明了开发人员对IT自动化、脚本化操作和管理工具的需求,这可以视为一种持续的向DevOps文化和实践的推进。 #### 关于C# - C# 是一种由微软开发的通用编程语言,它被广泛应用于开发Windows应用程序、服务器端Web应用程序以及移动和游戏开发。 - 在开发SSRSSubscriptionManager时,C# 语言的利用可能涉及到多种.NET框架中的类库,例如System.Xml用于解析和操作XML文件,System.Data用于数据库操作等。 - 使用C# 实现SSRS订阅管理器可以享受到.NET平台的诸多优势,比如类型安全、内存管理和跨平台兼容性。 #### 关于版本控制 - 版本控制是一种记录源代码文件更改历史的方法,它允许开发团队追踪和管理代码随时间的变化。常见的版本控制系统包括Git、Subversion等。 - 在SSRS订阅的上下文中,版本控制意味着可以追踪每个订阅设置的变更,从而保证订阅设置的一致性和可追溯性。 - SSRSSubscriptionManager通过使用XML文件,可以使得版本控制变得更加容易,因为XML文件可以被版本控制系统跟踪。 - 这种做法还确保了订阅设置文件的历史版本可以被审计,对企业的合规性和管理都有积极影响。 ### 结论 SSRSSubscriptionManager通过集成自动化、XML文件和版本控制,为SSRS订阅管理提供了更高效、可信赖和可管理的解决方案。使用C# 实现的这一工具能够极大提高IT专业人员在创建和维护SSRS订阅时的工作效率,并减少可能由手工操作引入的错误。通过强调自动化和可控制的文档处理,它也反映了IT行业的趋势,即追求效率、可靠性和版本管理。
recommend-type

图形缩放与平移实现全攻略:Delphi视图变换核心技术详解

# 摘要 本文系统探讨了图形缩放与平移技术的基本原理及其在实际开发中的应用,涵盖从数学基础到编程实现的全过程。文章首先介绍了图形变换的数学模型,包括坐标系统、矩
recommend-type

Unknown custom element: <CustomForm> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

在使用 Vue.js 时,如果遇到未知自定义组件 `<CustomForm>` 的错误提示,通常是由于组件注册过程中存在某些疏漏或错误。以下是常见的原因及对应的解决方案: ### 1. 组件未正确注册 确保 `<CustomForm>` 组件已经在使用它的父组件或全局中进行了注册。如果未注册,Vue 会提示该组件是未知的。 正确的注册方式如下: - **全局注册**(适用于所有组件都能访问的场景): ```javascript import CustomForm from '@/components/CustomForm.vue' Vue.component('CustomForm',
recommend-type

使用KnockoutJS开发的黑客新闻阅读器 hn-ko

在给定的文件信息中,我们可以提炼出以下IT相关知识点: ### 标题知识点 #### KnockoutJS - **KnockoutJS定义**:Knockout是一个轻量级的JavaScript库,它允许开发者利用声明式绑定方式创建富交互的Web应用程序。它特别擅长于实现UI的自动更新,当模型的数据发生变化时,视图会自动响应这些变化而更新,无需手动操作DOM。 - **KnockoutJS核心特性**: - **依赖项跟踪**:Knockout能够跟踪数据模型中的变化,当数据更新时自动更新相关联的UI元素。 - **声明式绑定**:开发者可以使用简单的数据绑定语法在HTML标记中直接指定数据与DOM元素之间的关系,这样可以使代码更加清晰和易于维护。 - **模板和自定义绑定**:Knockout提供了灵活的模板系统,可以创建可复用的UI组件,并通过自定义绑定来扩展其核心功能,以满足特定需求。 - **组件化**:Knockout支持创建独立的、可复用的视图模型组件,以构建复杂的用户界面。 ### 描述知识点 #### 入门和运行应用 - **Git克隆**:通过`git clone`命令可以从远程仓库克隆代码到本地环境,这是版本控制中常见的操作,有助于团队协作和代码共享。`https://github.com/crissdev/hn-ko.git`指向一个特定的GitHub仓库,其中包含着使用KnockoutJS编写的黑客新闻应用代码。 - **NPM(Node Package Manager)**:NPM是随Node.js一起安装的一个包管理工具,它用于安装和管理JavaScript项目依赖。`npm install`命令用于安装项目中的所有依赖项,这可能包括KnockoutJS库以及其他可能用到的库或框架。 - **启动应用**:`npm start`是启动脚本的命令,它通常在`package.json`文件的scripts部分定义,用以启动开发服务器或运行应用。 #### 麻省理工学院许可证 - **MIT许可证**:这是一种常见的开源许可证,允许用户在任何类型的项目中免费使用软件,无论是个人的还是商业的。在保留原作者版权声明的同时,用户可以根据自己的需要修改和分发代码。这是很多开源项目选择的许可证。 ### 标签知识点 #### JavaScript - **JavaScript作用**:JavaScript是一种高级的、解释执行的编程语言,它通常是运行在浏览器中的脚本语言,用于实现网页的动态效果和用户交互。JavaScript作为全栈开发的关键技术之一,也被广泛用于服务器端开发(Node.js)。 - **JavaScript特点**: - **事件驱动**:JavaScript可以响应用户的点击、输入等事件,并据此进行操作。 - **对象导向**:JavaScript支持面向对象编程,可以通过创建对象、继承、多态等特性来组织代码。 - **异步编程**:JavaScript支持异步编程模型,利用回调函数、Promises、async/await等技术,可以有效处理网络请求、用户输入等异步操作。 ### 压缩包子文件的文件名称列表知识点 - **hn-ko-master**:这表明压缩包中的文件是从名为`hn-ko`的GitHub仓库的`master`分支获取的。文件列表中的这个名称可以帮助开发者快速识别包含KnockoutJS项目的代码仓库版本。 ### 总结 以上知识点总结了文件信息中提及的关于KnockoutJS、Git、NPM、MIT许可证和JavaScript的核心概念和应用实践。KnockoutJS作为一个功能强大的前端库,特别适用于复杂用户界面的数据绑定和动态更新。而通过Git的使用可以方便地管理项目的版本,并与其他开发者协作。NPM则使得项目的依赖管理和模块化开发变得更加简单高效。MIT许可证为项目的使用者提供了法律上的许可,确保了软件使用的自由度。JavaScript作为一种多用途的编程语言,在前端开发中扮演了不可替代的角色。理解并运用这些知识点,将有助于进行现代Web应用的开发工作。
recommend-type

Delphi图层管理机制设计:打造高效绘图控件的架构之道

# 摘要 本文系统研究了Delphi图层管理机制的核心概念、理论基础与实现细节,重点分析了图层的数据模型、渲染流程及其交互机制。通过对图层容器设计、绘制性能优化与事件分发模型的深入探讨,提出了一个高效、可扩展的图层管理架构,并结合实际绘图控件开发,验证了该机制
recommend-type

激光slam14讲

激光SLAM(Simultaneous Localization and Mapping,同步定位与地图构建)是机器人领域中的关键技术之一,广泛应用于室内机器人、自动驾驶、无人机导航等领域。对于初学者来说,系统地学习相关理论和实践方法是入门的关键。以下是一些推荐的学习资料和学习路径,帮助你更好地掌握激光SLAM。 ### 推荐书籍与资料 1. **《视觉SLAM十四讲》**:虽然书名强调“视觉”,但其中的许多核心理论,如贝叶斯估计、卡尔曼滤波、因子图优化等,与激光SLAM有高度重合,是入门SLAM的必备读物。 2. **《概率机器人》**:这本书是SLAM领域的经典教材,深入讲解了粒子滤