MQTT协议笔记之连接和心跳 比眉伴天荒 2021-06-24 13:57 329阅读 0赞 ## 前言 ## 本篇会把连接(CONNECT)、心跳(PINGREQ/PINGRESP)、确认(CONNACK)、断开连接(DISCONNECT)和在一起。 ## CONNECT ## 像前面所说,MQTT有关字符串部分采用的修改版的UTF-8编码,CONNECT可变头部中协议名称、消息体都是采用修改版的UTF-8编码。前面基本上可变头部内容不多,下面是一个较为完整的CONNECT消息结构: <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="10"><strong>Fixed header/固定头部</strong></td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="4">Message Type(1)</td> <td colspan="1">DUP flag</td> <td colspan="2">QoS level</td> <td colspan="1">RETAIN</td> </tr> <tr> <td colspan="1"> <p>byte 1</p> </td> <td colspan="1"> </td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="10">Remaining Length</td> </tr> <tr> <td colspan="10"><strong>Variable header</strong>/可变头部</td> </tr> <tr> <td colspan="10">Protocol Name</td> </tr> <tr> <td colspan="1">byte 1</td> <td colspan="1">Length MSB (0)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1">Length LSB (6)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 3</td> <td colspan="1">'M'</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 4</td> <td colspan="1">'Q'</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 5</td> <td colspan="1">'I'</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 6</td> <td colspan="1">'s'</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 7</td> <td colspan="1">'d'</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 8</td> <td colspan="1">'p'</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> <tr> <td colspan="10">Protocol Version Number</td> </tr> <tr> <td colspan="1">byte 9</td> <td colspan="1">Version (3)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> </tr> <tr> <td colspan="10">Connect Flags</td> </tr> <tr> <td colspan="2"> </td> <td colspan="1">User Name Flag</td> <td colspan="1">Password Flag</td> <td colspan="1">Will Retain</td> <td colspan="2">Will QoS</td> <td colspan="1">Will Flag</td> <td colspan="1">Clean Session</td> <td colspan="1">Reserved</td> </tr> <tr> <td colspan="2"> <p>byte 10</p> </td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">x</td> </tr> <tr> <td colspan="10">Keep Alive timer</td> </tr> <tr> <td colspan="1">byte 11</td> <td colspan="1">Keep Alive MSB (0)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 12</td> <td colspan="1">Keep Alive LSB (10)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> </tr> <tr> <td colspan="10"><strong>Payload/消息体</strong></td> </tr> <tr> <td colspan="10"> <p>Client Identifier(客户端ID)</p> 1-23个字符长度,客户端到服务器的全局唯一标志,如果客户端ID超出23个字符长度,服务器需要返回码为2,标识符被拒绝响应的CONNACK消息。<br> 处理QoS级别1和2的消息ID中,可以使用到。<br> 必填项。</td> </tr> <tr> <td colspan="10"> <p>Will Topic</p> Will Flag值为1,这里便是Will Topic的内容。QoS级别通过Will QoS字段定义,RETAIN值通过Will RETAIN标识,都定义在可变头里面。</td> </tr> <tr> <td colspan="10"> <p>Will Message</p> Will Flag若设为1,这里便是Will Message定义消息的内容,对应的主题为Will Topic。如果客户端意外的断开触发服务器PUBLISH此消息。<br> 长度有可能为0。<br> 在CONNECT消息中的Will Message是UTF-8编码的,当被服务器发布时则作为二进制的消息体。</td> </tr> <tr> <td colspan="10"> <p>User Name</p> 如果设置User Name标识,可以在此读取用户名称。一般可用于身份验证。协议建议用户名为不多于12个字符,不是必须。</td> </tr> <tr> <td colspan="10"> <p>Password</p> 如果设置Password标识,便可读取用户密码。建议密码为12个字符或者更少,但不是必须。</td> </tr> </tbody> </table> ### 可变头部 ### 协议名称和协议版本都是固定的。 连接标志(Connect Flags) 一个字节表示,除了第1位是保留未使用,其它7位都具有不同含义。 业务上很重要,对消息总体流程影响很大,需要牢记。 Clean Session 0,表示如果订阅的客户机断线了,要保存为其要推送的消息(QoS为1和QoS为2),若其重新连接时,需将这些消息推送(若客户端长时间不连接,需要设置一个过期值)。 1,断线服务器即清理相关信息,重新连接上来之后,会再次订阅。 Will Flag 定义了客户端(没有主动发送DISCONNECT消息)出现网络异常导致连接中断的情况下,服务器需要做的一些措施。 简而言之,就是客户端预先定义好,在自己异常断开的情况下,所留下的最后遗愿(Last Will),也称之为遗嘱(Testament)。 这个遗嘱就是一个由客户端预先定义好的主题和对应消息,附加在CONNECT的可变头部中,在客户端连接出现异常的情况下,由服务器主动发布此消息。 只有在Will Flag位为1时,Will Qos和Will Retain才会被读取,此时消息体payload中要出现Will Topic和Will Message具体内容,否则,Will QoS和Will Retain值会被忽略掉。 Will Qos 两位表示,和PUBLISH消息固定头部的QoS level含义一样。这里先掠过,到PUBLISH消息再回过头来看看,会更明白些。 若标识了Will Flag值为1,那么Will QoS就会生效,否则会被忽略掉。 Will RETAIN 如果设置Will Flag,Will Retain标志就是有效的,否则它将被忽略。 当客户端意外断开服务器发布其Will Message之后,服务器是否应该继续保存。这个属性和PUBLISH固定头部的RETAIN标志含义一样,这里先掠过。 User name 和 password Flag: 用于授权,两者要么为0要么为1,否则都是无效。都为0,表示客户端可自由连接/订阅,都为1,表示连接/订阅需要授权。 Payload/消息体 消息体定义的消息顺序(如上表所示),约定俗成,不得更改,否则将可能引起混乱。 若Will Flag值为0,那么在payload中,Client Identifer后面就不会存在Will Topic和Will Message内容。 若User Name和Password都为0,意味着Payload/消息体中,找不到User Name和password的值,就算有,也是无效。标志决定着是否读取与否。 心跳时间(Keep Alive timer) 以秒为单位,定义服务器端从客户端接收消息的最大时间间隔。一般应用服务会在业务层次检测客户端网络是否连接,不是TCP/IP协议层面的心跳机制(比如开启SOCKET的SO\_KEEPALIVE选项)。 一般来讲,在一个心跳间隔内,客户端发送一个PINGREQ消息到服务器,服务器返回PINGRESP消息,完成一次心跳交互,继而等待下一轮。若客户端没有收到心跳反馈,会关闭掉TCP/IP端口连接,离线。 16位两个字节,可看做一个无符号的short类型值。最大值,2^16-1 = 65535秒 = 18小时。最小值可以为0,表示客户端不断开。一般设为几分钟,比如微信心跳周期为300秒。 Will Message编码 Will Message在CONNECT Payload/息体中,使用UTF-8编码。假设内容为“abcd”,大概如下: <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="1">byte 1</td> <td colspan="1">Length MSB (0)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1">Length LSB (4)</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 3</td> <td colspan="1">'a' (0x61)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 4</td> <td colspan="1">'b' (0x62)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 5</td> <td colspan="1">'c' (0x63)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 6</td> <td colspan="1">'d' (0x64)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> </tbody> </table> 有一点需要记住,PUBLISH的Payload/消息体中以二进制编码保存。 某刻客户端异常关闭触发服务器会PUBLISH此消息。那么服务器会直接把byte3-byte6之间字符取出,保存为二进制,附加到PUBLISH消息体中,大概存储如下: <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="1">byte 1</td> <td colspan="1">'a' (0x61)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1">'b' (0x62)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> </tr> <tr> <td colspan="1">byte 3</td> <td colspan="1">'c' (0x63)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> </tr> <tr> <td colspan="1">byte 4</td> <td colspan="1">'d' (0x64)</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> </tbody> </table> 另外,MQTT 3.1协议对Will message的说明很容易引起误解,3.1.1草案已经得到修正。 相关说明: http://mqtt.org/wiki/doku.php/will*message*utf8\_support https://tools.oasis-open.org/issues/browse/MQTT-2 ### 连接异常中断通知机制 ### CONNECT消息一旦设置在可变头部设置了Will flag标记,那就启用了Last-Will-And-Testament特性,此特性很赞。 一旦客户端出现异常中断,便会触发服务器发布Will Message消息到Will Topic主题上去,通知Will Topic订阅者,对方因异常退出。 ### 接收CONNECT后的响应动作 ### 接收到CONNECT消息之后,服务器应该返回一个CONNACK消息作为响应: 1. 若客户端绕过CONNECT消息直接发送其它类型消息,服务器应关闭此非法连接 若客户端发送CONNECT之后未收到CONNACT,需要关闭当前连接,然后重新连接 2. 相同Client ID客户端已连接到服务器,先前客户端必须断开连接后,服务器才能完成新的客户端CONNECT连接 客户端发送无效非法CONNECT消息,服务器需要关闭 ## CONNACK ## 一个完整的CONNACK消息大致如下: <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="10"><strong>Fixed header/固定头部</strong></td> </tr> <tr> <td colspan="1">byte 1</td> <td colspan="1"> </td> <td colspan="4">Message type (2)</td> <td colspan="1">DUP flag</td> <td colspan="2">QoS flags</td> <td colspan="1">RETAIN</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1"> </td> <td colspan="8">Remaining Length (2)</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">0</td> </tr> <tr> <td colspan="10"><strong>Variable header</strong>/可变头部</td> </tr> <tr> <td colspan="10">Topic Name Compression Response</td> </tr> <tr> <td colspan="1">byte 1</td> <td colspan="1">Reserved values. Not used.</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> </tr> <tr> <td colspan="10">Connect Return Code</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1">Return Code</td> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1"> </td> </tr> </tbody> </table> 可变头部第一个字节为保留,无甚用处。第二个字节为连接握手返回码: <table> <thead> <tr> <td colspan="1">返回值</td> <td colspan="1">16进制</td> <td colspan="1">含义</td> </tr> </thead> <tbody> <tr> <td colspan="1">0</td> <td colspan="1">0x00</td> <td colspan="1">Connection Accepted</td> </tr> <tr> <td colspan="1">1</td> <td colspan="1">0x01</td> <td colspan="1">Connection Refused: unacceptable protocol version</td> </tr> <tr> <td colspan="1">2</td> <td colspan="1">0x02</td> <td colspan="1">Connection Refused: identifier rejected</td> </tr> <tr> <td colspan="1">3</td> <td colspan="1">0x03</td> <td colspan="1">Connection Refused: server unavailable</td> </tr> <tr> <td colspan="1">4</td> <td colspan="1">0x04</td> <td colspan="1">Connection Refused: bad user name or password</td> </tr> <tr> <td colspan="1">5</td> <td colspan="1">0x05</td> <td colspan="1">Connection Refused: not authorized</td> </tr> <tr> <td colspan="1">6-255</td> <td colspan="1"> </td> <td colspan="1">Reserved for future use</td> </tr> </tbody> </table> 只有0-5目前被使用到,其他值有待日后使用。一般返回值为0x00,表示连接建立。非法的请求,需要返回相应的数值。 从上面看出,一个CONNACT,四个字节表示。一个正常的CONNACT消息实际内容可能如下: 0x20 0x02 0x00 0x00 若是在私有协议中,两个字节就足够了。 很多时候,客户端和服务器端在没有消息传递时,会一直保持着连接。虽然不能依靠TCP心跳机制(比如SO\_KEEPALIVE选项),业务层面定义心跳机制,会让连接状态检测、控制更为直观。 ## PINGREQ ## 由客户端发送到服务器端,证明自己还在一直连接着呢。两个字节,固定值。 <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="10"><strong>Fixed header/固定头部</strong></td> </tr> <tr> <td colspan="1">byte 1</td> <td colspan="1"> </td> <td colspan="4">Message type (12)</td> <td colspan="1">DUP flag</td> <td colspan="2">QoS flags</td> <td colspan="1">RETAIN</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1"> </td> <td colspan="8">Remaining Length (0)</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> </tbody> </table> 客户端会在一个心跳周期内发送一条PINGREQ消息到服务器端。 心跳频率在CONNECT可变头部“Keep Alive timer”中定义时间,单位为秒,无符号16位short表示。 ## PINGRESP ## 服务器收到PINGREQ请求之后,会立即响应一个两个字节固定格式的PINGRESP消息。 <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="10"><strong>Fixed header/固定头部</strong></td> </tr> <tr> <td colspan="1">byte 1</td> <td colspan="1"> </td> <td colspan="4">Message type (13)</td> <td colspan="1">DUP flag</td> <td colspan="2">QoS flags</td> <td colspan="1">RETAIN</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">1</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1"> </td> <td colspan="8">Remaining Length (0)</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> </tbody> </table> 服务器一般若在1.5倍的心跳周期内接收不到客户端发送的PINGREQ,可考虑关闭客户端的连接描述符。此时的关闭连接的行为和接收到客户端发送DISCONNECT消息的处理行为一致,但对客户端的订阅不会产生影响(不会清除客户端订阅数据),这个需要牢记。 若客户端发送PINGREQ之后的一个心跳周期内接收不到PINGRESP消息,可考虑关闭TCP/IP套接字连接。 ## DISCONNECT ## 客户端主动发送到服务器端,表明即将关闭TCP/IP连接。此时要求服务器要完整、干净的进行断开处理,不能仅仅类似于关闭连接描述符类似草草处理之。 需要两个字节,值固定: <table> <thead> <tr> <th colspan="1"> </th> <th colspan="1">Description</th> <th colspan="1">7</th> <th colspan="1">6</th> <th colspan="1">5</th> <th colspan="1">4</th> <th colspan="1">3</th> <th colspan="1">2</th> <th colspan="1">1</th> <th colspan="1">0</th> </tr> </thead> <tbody> <tr> <td colspan="10"><strong>Fixed header/固定头部</strong></td> </tr> <tr> <td colspan="1">byte 1</td> <td colspan="1"> </td> <td colspan="4">Message type (14)</td> <td colspan="1">DUP flag</td> <td colspan="2">QoS flags</td> <td colspan="1">RETAIN</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">1</td> <td colspan="1">0</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> <td colspan="1">x</td> </tr> <tr> <td colspan="1">byte 2</td> <td colspan="1"> </td> <td colspan="8">Remaining Length (0)</td> </tr> <tr> <td colspan="1"> </td> <td colspan="1"> </td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> <td colspan="1">0</td> </tr> </tbody> </table> 服务器要根据先前此客户端在发送CONNECT消息可变头部Connect flag中的“Clean session flag”所设置值,再次复习一下: 1. 值为0,服务器必须在客户端断开之后继续存储/保持客户端的订阅状态。这些状态包括: * 存储订阅的消息QoS1和QoS2消息 * 正在发送消息期间连接丢失导致发送失败的消息 * 以便当客户端重新连接时以上消息可以被重新传递。 2. 值为1,服务器需要立刻清理连接状态数据。 有一点需要牢记,服务器在接收到客户端发送的DISCONNECT消息之后,需要主动关闭TCP/IP连接。
还没有评论,来说两句吧...