MQTT learn

MQTT是什么

  • 由于网上的资料太繁杂,看了好久也没很明白。(目前只是大概了解一下)
  • 这里写图片描述
  • 这里写图片描述
  • You need to understand few concepts:
  • 这里写图片描述

Publish/Subscribe System

  • 这里写图片描述
  • Message: information exchanged between your devices - command or data.

Topics

  • 这里写图片描述
  • Topics are case sensitive.

Broker

  • MQTT解耦了发布者和订阅者,所以任何客户端都只与中间人(broker)建立连接。
  • 客户端泛指MQTT的客户端,包含发布者和订阅者;分别负责发布消息和订阅消息。
  • Broker相当于是中间人。其被称为发布/订阅协议的心脏部分,根据具体的实现不同,一个broker可以支持数以千计的客户端并发连接。
  • 这里写图片描述

安装MQTT和Java

  • 因为我司需要使用MQTT协议,所以就去学习了一下,然后看得有点眼花缭乱。(什么EMQTT啊。。。)
  • 最后找到了一篇极棒的博文,帮助了我。
  • 极棒的博文
  • 我就是先把它的github项目clone了下来,然后按照它说的运行,然后就比较简单地理解了一下Publisher和Subscriber的意思。
  • 在运行的过程中,因为一开始没有装broker,所以一直无法跑。
  • 然后broker在windows系统上又没有64位的,所以这里我尝试将它放到CentOS环境下进行安装。(又找到了一篇极棒的博文教程,发现稍微详细一点的教程都是英文的。)
  • 这里我安装的broker是mosquitto。(只需要看下面教程的Step1就可以了)
  • 极棒的教程2
  • 那么这个项目对于后端来说是可行的。
  • 然后还需要和前端的进行交互,前端是使用JS进行编写的。

前端和后端实时通信

  • 这里说的实时通信就是指后端可以将消息推送到前端,当然我没有弄什么心跳协议。。
  • 这里又发现了一篇很棒的教程帮助我更快地入门(感谢这些写的很棒的教程)。
  • 很棒的教程3
  • 一些注意事项:
  • mosquitto的websocket端口默认是9001,java那儿是1883。
  • 我按照它的运行代码后发现会连接不上,后来看了评论区的求助,发现是没有开启本地mqtt服务器websocket端口
  • 那么就要让mosquitto去支持websocket了,这里我又发现了一个很好的教程。
  • 很棒的教程4
  • 因为我mosquito是yum安装的,所以这里没有像它那样的配置文件。所以直接做第四步就好了。
  • 最后就可以看到运行结果了。
  • 这里写图片描述
<think>我们正在讨论的是MQTT客户端库中的函数,具体是`mqtt_parse_incoming`。根据引用内容,我们没有直接看到关于`mqtt_parse_incoming`的说明,但我们可以根据MQTT协议和常见客户端库的实现来推断这个函数的作用和用法。 在MQTT客户端库中,`mqtt_parse_incoming`函数通常用于解析从MQTT代理(broker)接收到的原始网络数据,将其解析为MQTT报文。这个函数是客户端内部处理网络数据流的关键部分,负责识别完整的报文,解析报文类型,并处理每个报文的内容。 以下是对`mqtt_parse_incoming`的详细介绍和使用指南: ### 1. 函数作用 `mqtt_parse_incoming`的主要作用是解析从网络接收到的原始字节流,从中提取出完整的MQTT报文,并根据报文类型进行相应的处理。这包括: - 确定报文的类型(如CONNACK, PUBLISH, SUBACK等) - 解析报文的可变报头和有效载荷 - 处理报文(例如,将收到的PUBLISH报文传递给应用层的回调函数) ### 2. 函数原型 根据常见的MQTT库(如Eclipse Paho),类似的函数可能有不同的命名,但功能相似。假设一个典型的函数原型如下: ```c int mqtt_parse_incoming(mqtt_client_t *client, uint8_t *data, size_t len); ``` - **参数**: - `client`: MQTT客户端实例,包含连接状态、回调函数等。 - `data`: 指向接收到的字节流的指针。 - `len`: 接收到的数据长度。 - **返回值**: - 成功解析的字节数,或者错误码(负数)。 ### 3. 使用场景 这个函数通常在网络层接收到数据后被调用。例如,当使用socket接收到数据后,应用程序将接收到的数据传递给这个函数进行解析。 ### 4. 使用步骤 假设我们有一个MQTT客户端,其网络接收循环大致如下: ```c uint8_t buffer[1024]; int received_len; while ((received_len = recv(sockfd, buffer, sizeof(buffer), 0)) > 0) { int processed = mqtt_parse_incoming(&client, buffer, received_len); if (processed < 0) { // 处理解析错误 break; } // 如果解析消耗的数据少于接收的数据,可能需要处理剩余数据(通常不会,因为MQTT报文是连续的) } ``` ### 5. 内部处理逻辑 在`mqtt_parse_incoming`函数内部,通常会按照以下步骤工作: 1. **检查当前解析状态**:如果之前已经解析了部分报文(比如固定报头只解析了一部分),则继续解析剩余部分。 2. **解析固定报头**:第一个字节包含报文类型和标志,后面跟着剩余长度(可变长度编码)。 3. **根据剩余长度读取可变报头和有效载荷**:直到一个完整的报文被解析出来。 4. **根据报文类型调用相应的处理函数**: - 如果是PUBLISH消息,则调用应用层设置的消息回调函数。 - 如果是SUBACK,则确认订阅完成。 - 等等。 5. **更新缓冲区**:已解析的数据被移除,剩余数据(如果有)用于下一次解析。 ### 6. 错误处理 - **报文格式错误**:例如,无法解析剩余长度、报文类型无效等,应返回错误码并断开连接(根据MQTT规范)。 - **缓冲区不足**:如果接收到的数据不足以构成一个完整的报文,函数可能会返回0,表示需要更多数据。 ### 7. 注意事项 - **线程安全**:在嵌入式系统中,这个函数可能是非线程安全的,需要在同一个上下文中调用。 - **粘包处理**:由于TCP是流式协议,一次接收的数据可能包含多个报文或者一个报文的片段,因此解析函数需要能够处理部分报文。 - **报文最大长度**:库通常有限制报文的最大长度,以防止拒绝服务攻击。 ### 8. 示例代码 以下是一个简化的示例,展示如何集成这个函数: ```c void data_received_callback(int sockfd, void *arg) { mqtt_client_t *client = (mqtt_client_t*)arg; uint8_t buffer[1024]; int len = recv(sockfd, buffer, sizeof(buffer), 0); if (len > 0) { int processed = mqtt_parse_incoming(client, buffer, len); if (processed < 0) { printf("Parse error: %d\n", processed); disconnect_client(client); } } else if (len == 0) { // 连接关闭 disconnect_client(client); } else { // recv错误 perror("recv"); } } ``` ### 9. 引用说明 本文档参考了MQTT协议规范以及常见MQTT库(如Paho)的实现[^1][^3]。 --- ### 相关问题 1. MQTT报文的结构是怎样的?固定报头和可变报头分别包含哪些内容? 2. 在嵌入式系统中如何处理MQTT报文的分片和粘包问题? 3. MQTT客户端库中如何实现心跳机制(PINGREQ/PINGRESP)? 4. 如何处理QoS1和QoS2级别的消息传递? 5. MQTT_CONNACK报文中的返回码有哪些?分别代表什么含义? [^1]: MQTT协议规范 [^3]: MQTT及Webscoket的介绍及使用 </think>### MQTT_parse_incoming 详细介绍及使用指南 `mqtt_parse_incoming` 是 MQTT 客户端库的核心报文解析函数,用于处理从 MQTT 代理(broker)接收的原始网络数据流。其主要功能是将字节流转换为结构化的 MQTT 报文对象,并进行协议验证和错误处理。以下是详细解析: #### 一、核心功能原理 1. **报文解析流程**: ```mermaid graph LR A[原始字节流] --> B{解析固定报头} B --> C[读取控制报文类型] C --> D[解析剩余长度] D --> E{解析可变报头} E --> F[根据报文类型解析] F --> G[解析有效载荷] G --> H[生成报文对象] ``` 1. **固定报头解析**:读取第1字节获取控制报文类型(4bit)和标志位(4bit) $$ \text{报文类型} = (\text{byte1} \gg 4) \& 0x0F $$ 2. **剩余长度解析**:采用可变长度编码(最多4字节) 3. **可变报头解析**:根据报文类型解析报文ID等字段 4. **有效载荷解析**:如PUBLISH报文的消息内容 2. **错误处理机制**: - 无效报文类型:返回`MQTT_ERR_PROTOCOL` - 长度不匹配:返回`MQTT_ERR_MALFORMED_PACKET` - QoS冲突:返回`MQTT_ERR_QOS_NOT_SUPPORTED` #### 二、典型函数原型(基于lwIP实现) ```c err_t mqtt_parse_incoming( struct mqtt_client *client, // MQTT客户端实例 u8_t *data, // 输入字节流缓冲区 u16_t len, // 数据长度 u16_t *offset // 解析完成的字节数(输出) ); ``` **返回值**: - `ERR_OK`:解析成功 - `ERR_VAL`:协议错误 - `ERR_MEM`:内存不足 - `ERR_BUF`:缓冲区不完整 #### 三、使用场景与调用模式 1. **网络接收循环中的典型调用**: ```c u8_t buffer[512]; u16_t offset = 0; while (recv_data_available()) { u16_t recv_len = network_recv(buffer + offset, sizeof(buffer) - offset); // 解析接收数据 err_t err = mqtt_parse_incoming(&client, buffer, offset + recv_len, &offset); if (err != ERR_OK) { handle_error(err); // 错误处理 } // 处理剩余数据 if (offset > 0) { memmove(buffer, buffer + offset, recv_len - offset); } } ``` 2. **报文处理流程**: ```c switch (parsed_packet->type) { case MQTT_MSG_TYPE_PUBLISH: handle_publish(parsed_packet); // 处理发布消息 break; case MQTT_MSG_TYPE_SUBACK: handle_suback(parsed_packet); // 处理订阅确认 break; case MQTT_MSG_TYPE_PINGRESP: reset_ping_timer(); // 重置心跳计时器 break; } ``` #### 四、关键实现细节 1. **剩余长度解码算法**: ```c u32_t multiplier = 1; u32_t remaining_length = 0; u8_t encoded_byte; do { encoded_byte = *data++; remaining_length += (encoded_byte & 0x7F) * multiplier; multiplier *= 128; } while ((encoded_byte & 0x80) != 0); ``` 2. **QoS级别验证**(PUBLISH报文): ```c if ((fixed_header_flags & 0x06) == 0x06) { return ERR_VAL; // QoS 3为非法值 } ``` #### 五、最佳实践与注意事项 1. **缓冲区管理**: - 使用循环队列处理分包(参考引用[^4]) - 建议实现预分配内存池避免碎片化 2. **安全防护**: ```c // 防止长度溢出攻击 if (remaining_length > MAX_ALLOWED_PACKET_SIZE) { return ERR_VAL; } ``` 3. **状态机集成**: - 在`CONNECT`状态拒绝非`CONNACK`报文 - 在未连接状态忽略所有非控制报文 #### 六、调试与错误排查 1. **常见错误码**: | 错误码 | 含义 | |-------------|--------------------------| | ERR_ARG | 无效参数 | | ERR_PROTO | 违反MQTT协议规范 | | ERR_WOULDBLOCK | 需要更多数据 | 2. **日志调试**: ```c #define MQTT_DEBUG(fmt, ...) \ printf("[MQTT] " fmt "\n", ##__VA_ARGS__) void parse_error(err_t err) { if (err == ERR_PROTO) { MQTT_DEBUG("Protocol violation at byte %u", current_offset); } } ``` --- ### 相关问题 1. MQTT报文的结构包含哪些必要组成部分? 2. 如何处理MQTT协议中QoS2级别的消息传递? 3. 在资源受限设备中如何优化MQTT报文解析的内存使用? 4. MQTT心跳机制(PINGREQ/PINGRESP)如何实现? 5. MQTT协议错误与网络层错误应如何区分处理? [^1]: Adafruit_MQTT 源码解析 [^3]: MQTT及Webscoket的介绍及使用 [^4]: MQTT_learn中的队列实现
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值