【IM即时通讯】MQTT协议的详解(2)- CONNECT Packet

【IM即时通讯】MQTT协议的详解(2)- CONNECT Packet



前言

关于所有的类型的数据示例已经在上面一篇博客说完:
【IM即时通讯】MQTT协议的详解回顾的同学可以直达这里

说明

CONNECT Packet此包是客户端与服务端建立连接后,发送的第一个包,且第一个包必须是此包。在一个连接中,该包只能发送一次。若发送了多次,当服务器第二次收到该包时,应该作为违法处理,立即断开连接。


一、固定同步详解、可变头部详解

  • 固定头部
+---------------------------------------------------------+
|   bit   |  7  |  6  | 5   |  4  |  3  |  2  |  1  |  0  |
+---------------------------------------------------------+
|  byte1  |  0  |  0  | 0   |  1  |  0  |  0  |  0  |  0  |
+---------------------------------------------------------+
| byte2...|              Remaining Length                 |
+---------------------------------------------------------+
  • 可变头部
+--------+----------------+---+---+---+---+---+---+---+---+
|  Bit   |   Description  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------+
| byte 1 |  Length MSB (0)| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+---------------------------------------------------------+
| byte 2 |  Length LSB (4)| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
+---------------------------------------------------------+
| byte 3 |       'M'      | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
+---------------------------------------------------------+
| byte 4 |       'Q'      | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
+---------------------------------------------------------+
| byte 5 |       'T'      | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
+---------------------------------------------------------+
| byte 6 |       'T'      | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
+---------------------------------------------------------+
| byte 7 |     Level(4)   | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
+---------------------------------------------------------+
| byte 8 |        x       | x | x | x | x | x | x | x | 0 |
+--------+----------------+---+---+---+---+---+---+---+---+
| byte 9 |                 Keep Alive MSB                 |
+---------------------------------------------------------+
| byte 10|                 Keep Alive LSB                 |
+--------+----------------+---+---+---+---+---+---+---+---+

其中,

  • 如前面介绍,前 2 个字节是包 ID。
  • 3 ~ 6 字节是协议名称,字符使用 UTF-8 编码;
  • 第 7 个字节是协议等级(协议版本。3.1.1 版本对应的协议等级是 4
  • 第8个字节包含一些连接标记位。如下图所示:
BitDescription
7是否有用户名字标志位英文:【User Name Flag】
6是否有密码标志位 英文:【Password Flag】
5决定遗嘱消息是否被服务器保留英文:【Will Retain】
4~3决定遗嘱消息的服务质量等级 英文:【Will QoS】
2指示是否有遗嘱消息需要在连接断开时发布 英文:【Will Flag】
1决定是否清除之前的会话信息 英文:【Clear session】
0保留位,必须设置为0 (也有可能不为0)英文:【Reserved】

注意

若服务端不支持客户端协议版本,需要响应一个 CONNACK 包,指定 code 为 0x01,然后断开连接.服务端需校验 Reserved 位。若值不为 0,需断开连接。

  • Clear session:用来指定对 Session 的处理方式。若值为0,在服务端和客户端断开连接后,它们都要保存 Session 信息,以便再次连上时恢复之前 Session 中的信息。除此之外,服务端还需要在断开连接后保存 QoS 1 和 QoS 2 消息和客户端的订阅内容;若值为1,当客户端和服务端连接上时,必须丢弃之前的 Session 状态信息再创建一个新的 Session 和订阅内容。

    • 位置:连接标识字节的第1位(Bit 1)

    • 在客户端,Session 状态信息包括:
      已被发送到服务端,但还没有被确认的 QoS 1 和 QoS 2 消息;
      已被服务器接收,但还没有被确认的 QoS 2 消息。
      在服务端,Session 状态信息包括:

    • 客户端的订阅内容;
      已被发送到服务端,但还没有被确认的 QoS 1 和 QoS 2 消息;
      待发送到客户端的QoS 1 和 QoS 2 消息;
      已被客户端接收,但还没有被确认的QoS 2 消息;
      (可选)待发送到客户端的QoS 0 消息;

  • Will Flag,Will QoS,Will Retain:这三个字段是用来预立“遗嘱”的。预立“遗嘱”的意思是:客户端在连接服务端时,可将预先定义好的主题和对应消息发送给服务端。当它和服务端连接断开时,服务端将及时地发布这段消息到预定的主题。

    • Will Flag:
      位置:连接标识字节的第2位(Bit 2)
      含义:此标志位指示客户端是否指定了一个遗嘱(Will)消息。如果Will Flag 被设置为1,表明客户端希望在连接意外断开时,服务器能够发布一个预定义的遗嘱消息到指定的遗嘱主题。如果设置为0,则没有遗嘱消息被指定。
    • Will QoS (遗嘱服务质量);
      位置:连接标识字节的第3和第4位(Bit 3 和 Bit 4)
      含义:这两个位共同表示遗嘱消息的服务质量(QoS)。QoS 级别可以是0(最多一次交付),1(至少一次交付),或2(恰好一次交付)。只有当Will Flag 被设置为1时,这两个位才有效。
    • Will Retain:
      位置:连接标识字节的第5位(Bit 5)
      含义:此标志位决定遗嘱消息是否被服务器保留。如果Will Retain 被设置为1,服务器将保留遗嘱消息,并将其作为新订阅者的默认消息。如果设置为0,遗嘱消息将不会被服务器保留。
  • Password Flag:表示客户端将提供密码进行认证。如果设置为0,则不包含密码信息。

    • 位置:连接标识字节的第6位(Bit 6)
      含义:此标志位指示是否在CONNECT Packet 的载荷中包含密码信息。如果Password Flag 被设置为1
  • User Name Flag:此标志位指示是否在CONNECT Packet 的载荷中包含用户名信息。

    • 位置:连接标识字节的第7位(Bit 7)
    • 如果User Name Flag 被设置为1,表示客户端将提供用户名进行认证。如果设置为0,则不包含用户名信息。

二、载荷内容详解

CONNECT 包的载荷一定包含 Client Identifier 字段,可能包含 Will Topic, Will Message, User Name, Password 字段(由可变头部中的各标记位决定)。这些字段若存在,一定要按照以上顺序排列。

  • Client Identifier:由客户端自己指定的 ID,服务端据此来标识客户端(以此关联Session)。因此不同客户端之间的 ID 不能重复(重复将视为同一客户端)。它使用 UTF-8编码,长度通常在1 ~ 23个字节之间,通常包含 [0-9a-zA-Z] 中的字符(允许例外,由服务端的实现决定)。若客户端 ID 不存在,服务端需要为其指定一个独一无二的 ID。在这种情况下,客户端必须设置 CleanSession 为 1;若不设为 1,服务端需要 响应 CONNACK 包,其中 返回 code 0x02(Identifier rejected), 随后断开连接。
  • Will Topic,Will Message:当 Will Flag 值为 1 时存在,均采用 UTF-8 编码。
  • User Name:采用 UTF-8 编码,用来做身份认证。
  • Password:长度不固定,头两个字节用来指明密码的字节数,之后是密码的字节,结构如下:
+-----------+---+---+---+---+---+---+---+---+
|    Bit    | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------+---+---+---+---+---+---+---+
|  byte 1   |        Data length MSB        |
+-------------------------------------------+
|  byte 2   |        Data length LSB        |
+-------------------------------------------+
|  byte 3...|      Data, if length > 0.     |
+-------------------------------------------+

在 MQTT 协议中,字符串均采用的 UTF-8 编码

+------------------------------------------------------------+
|   Bit   |  7  |  6  | 5   |  4  |  3  |  2  |  1  |    0   |
+------------------------------------------------------------+
|  byte1  |                 String length MSB                |
+------------------------------------------------------------+
|  byte2  |                 String length LSB                |
+------------------------------------------------------------+
| byte3...|    UTF-8 Encoded Character Data, if length > 0   |
+------------------------------------------------------------+

一些情况的处理方式:
注意:在一个客户端在线的情况下,同一客户端(相同 Client ID)再次连接服务端,服务端需断开之前的连接;客户端在发送 CONNECT 包后,可以再立即发送其他的包,而无需等待 CONNACK 包的响应。但服务端收到 CONNECT 包后,若拒绝连接,一定不能处理客户端在 CONNECT 包后发送的包。


总结

下一个类型:CONNACK Packet

uint8 MQTT_PacketConnect(const int8 *user, const int8 *password, const int8 *devid, uint16 cTime, uint1 clean_session, uint1 qos, const int8 *will_topic, const int8 *will_msg, int32 will_retain, MQTT_PACKET_STRUCTURE *mqttPacket) { uint8 flags = 0; uint8 will_topic_len = 0; uint16 total_len = 15; int16 len = 0, devid_len = strlen(devid); if(!devid) return 1; total_len += devid_len + 2; //断线后,是否清理离线消息:1-清理 0-不清理-------------------------------------------- if(clean_session) { flags |= MQTT_CONNECT_CLEAN_SESSION; //0x02 } //异常掉线情况下,服务器发布的topic------------------------------------------------------ if(will_topic) { flags |= MQTT_CONNECT_WILL_FLAG; //0x04 will_topic_len = strlen(will_topic); total_len += 4 + will_topic_len + strlen(will_msg); } //qos级别--主要用于PUBLISH(发布态)消息的,保证消息传递的次数----------------------------- switch((unsigned char)qos) { case MQTT_QOS_LEVEL0:/**< 最多发送一次 */ flags |= MQTT_CONNECT_WILL_QOS0;// 0x00 break; case MQTT_QOS_LEVEL1:/**< 最少发送一次 */ flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS1); //0x04 | 0x08 break; case MQTT_QOS_LEVEL2:/**< 只发送一次 */ flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS2); //0x04 | 0x10 break; default: return 2; } //主要用于PUBLISH(发布态)的消息,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它。如果不设那么推送至当前订阅的就释放了 if(will_retain) { flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_RETAIN); //0x04 | 0x20 } //账号为空 密码为空--------------------------------------------------------------------- if(!user || !password) { return 3; } flags |= MQTT_CONNECT_USER_NAME | MQTT_CONNECT_PASSORD; //0x80 | 0x40 total_len += strlen(user) + strlen(password) + 4; //分配内存----------------------------------------------------------------------------- MQTT_NewBuffer(mqttPacket, total_len); if(mqttPacket->_data == NULL) return 4; memset(mqttPacket->_data, 0, total_len); /*************************************固定头部***************************
最新发布
03-09
<think>好的,我现在需要详细回答用户关于MQTT连接包构建函数`uint8 MQTT_PacketConnect`的实现细节,特别是参数作用、标志设置、遗嘱消息处理、QoS级别选择和用户名密码验证。首先,我得回忆一下MQTT协议的基础知识,尤其是CONNECT报文的结构。 根据用户提供的引用,特别是引用[3]中提到CONNECT包需要设置用户名和密码标志位,并且涉及QoS的设置。首先,CONNECT报文是客户端连接到代理时发送的第一个包,包含协议名称、版本、连接标志、保持连接时间、客户端ID、遗嘱主题、遗嘱消息、用户名和密码等信息。 接下来,函数参数部分。用户询问各个参数的作用,比如clientID、keepAlive、username、password等。需要说明每个参数在协议中的对应位置,例如clientID是必须的,如果客户端没有设置,某些代理可能会生成一个,但根据MQTT 3.1.1规范,如果Clean Session为1,可以允许空ID。但引用[3]提到不允许匿名登录,因此用户名和密码标志必须置1,所以参数中需要包含这些信息。 然后是连接标志flags的设置。连接标志字节包含多个选项,比如Clean Session、Will Flag、Will QoS、Will Retain、Password和Username标志。每个bit位代表不同的设置。例如,Clean Session位决定是否清除之前的会话,Will Flag表示是否存在遗嘱消息,Will QoS占两位,指定遗嘱消息的QoS级别,Will Retain决定遗嘱消息是否保留。最后两位是用户名和密码是否存在。 遗嘱消息处理部分需要解释Will Topic和Will Message如何被包含在报文中,这取决于Will Flag是否设置。如果设置了Will Flag,那么遗嘱主题和消息会被添加到有效载荷中,同时QoS和Retain标志也会影响遗嘱消息的发布方式。 QoS级别选择方面,引用[3]提到需要掌握QoS服务质量,所以需要说明在连接标志中如何设置Will QoS字段,比如0、1、2对应不同的服务质量等级,影响遗嘱消息的传递保证。 用户名和密码验证部分,根据引用[3],用户名和密码标志必须设置为1,因此函数参数中需要包含用户名和密码,并将它们添加到有效载荷中。需要注意的是,即使标志位设置为1,密码字段是可选的,但根据代理配置可能需要同时提供用户名和密码。 在实现逻辑上,函数需要按照MQTT协议规范将各个参数转换为字节流。首先构建固定报头,然后构建可变报头,包括协议名称、版本、连接标志、保持连接时间。接着是有效载荷部分,依次添加客户端ID、遗嘱主题、遗嘱消息、用户名和密码。每个字段的长度前缀需要正确计算,例如UTF-8编码字符串前需要两个字节表示长度。 需要处理错误情况,比如参数不符合规范时返回错误码,例如无效的QoS级别或者缺少必要的参数。同时,确保内存分配正确,避免缓冲区溢出。 可能还需要参考引用[4]中的资源管理,确保在构建报文时正确分配和释放内存,尤其是在处理动态内容如遗嘱消息和用户名密码时。 最后,生成相关问题时要涵盖协议结构、参数影响、错误处理和应用场景,帮助用户深入理解相关知识点。</think>### MQTT_PacketConnect函数实现详解 #### 1. 函数功能 `MQTT_PacketConnect`函数负责构造MQTT协议CONNECT报文,用于客户端与代理建立连接。其核心是填充协议要求的固定报头、可变报头及有效载荷,并处理连接标志位、遗嘱消息、QoS级别及认证信息。 --- #### 2. 参数作用 | 参数名 | 作用 | |-----------------|----------------------------------------------------------------------| | **clientID** | 客户端唯一标识符,必须为非空UTF-8字符串(Clean Session=0时可为空)[^3] | | **keepAlive** | 心跳间隔(秒),若为0则禁用保活机制 | | **username** | 代理要求的认证用户名,若为空则用户名标志位置0 | | **password** | 认证密码,若为空则密码标志位置0(需与用户名同时存在)[^3] | | **willTopic** | 遗嘱消息主题(Will Flag=1时必填) | | **willMsg** | 遗嘱消息内容(需与willTopic成对出现) | | **willQoS** | 遗嘱消息的QoS等级(0/1/2),影响传输可靠性 | | **willRetain** | 是否将遗嘱消息保留为代理的最后一条消息(0/1) | | **cleanSession**| 是否清除历史会话(1=新建连接,0=恢复会话) | --- #### 3. 连接标志位(Flags)设置 标志位占用1字节(8 bits),按以下顺序组合: ``` | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |----|----|----|----|----|----|----|----| | 用户名标志 | 密码标志 | Will Retain | Will QoS | Will Flag | Clean Session | 保留位 | ``` - **Clean Session(Bit 1)**:设为1时代理会丢弃该客户端的持久会话[^2]。 - **Will Flag(Bit 2)**:设为1时需填充`willTopic`和`willMsg`。 - **Will QoS(Bit 3-4)**:指定遗嘱消息的QoS级别(如`willQoS=2`则设为`0b10`)。 - **Will Retain(Bit 5)**:设为1时代理保留遗嘱消息。 - **用户名/密码标志(Bit 6-7)**:若`username`非空则Bit 6=1;若`password`非空则Bit 7=1且Bit 6必须为1。 --- #### 4. 遗嘱消息处理逻辑 1. **条件检查**:当`Will Flag=1`时,校验`willTopic`和`willMsg`非空。 2. **编码规则**:将`willTopic`和`willMsg`按UTF-8编码写入有效载荷,前导2字节为长度。 3. **QoS与Retain**:根据`willQoS`和`willRetain`设置标志位,影响消息发布行为。 --- #### 5. QoS级别选择 - **QoS 0(最多一次)**:不保证送达,适用于低优先级数据。 - **QoS 1(至少一次)**:需代理确认,可能重复(如温度上报)。 - **QoS 2(恰好一次)**:严格保证唯一性(如支付指令)[^3]。 --- #### 6. 用户名密码验证 - **标志位依赖**:若`password`非空,则`username`必须存在(Bit 6=1且Bit 7=1)。 - **数据编码**:将`username`和`password`按UTF-8格式写入有效载荷,前导长度前缀。 - **安全性**:密码明文传输,建议配合TLS加密(部分实现支持加密哈希)[^1]。 --- #### 7. 报文构造示例 ```c // 伪代码示例 void MQTT_PacketConnect() { // 固定报头:报文类型(0x10) + 剩余长度 writeByte(0x10); writeVariableLength(remaining_len); // 可变报头:协议名+"MQTT"、版本0x04、标志位、KeepAlive writeUTF8("MQTT"); writeByte(0x04); writeByte(flags); // 组合所有标志位 writeShort(keepAlive); // 有效载荷:ClientID、WillTopic、WillMsg、Username、Password writeUTF8(clientID); if (WillFlag) { writeUTF8(willTopic); writeUTF8(willMsg); } if (UsernameFlag) writeUTF8(username); if (PasswordFlag) writeUTF8(password); } ``` --- #### 8. 错误处理 - **无效QoS**:若`willQoS`非0/1/2,返回错误码。 - **字段缺失**:如WillFlag=1但未提供遗嘱主题,触发异常。 - **内存溢出**:动态计算报文长度,避免缓冲区越界。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值