MQTT 会话
什么是会话?
我们将从客户端向服务端发起 MQTT 连接请求开始,到连接中断直到会话过期为止的消息收发序列称之为会话。因此,会话可能仅持续一个网络连接,也可能跨越多个网络连接存在,如果客户端能在会话过期之前重新建立了连接的话。
在 MQTT v5 中会话过期时间由 Session Expiry Interval 字段决定,早前版本的协议没有限制会话过期时间,但通常由 MQTT 服务端决定。
什么是会话状态?
MQTT 要求客户端与服务端在会话有效期内存储一系列与客户端标识相关联的状态,称之为会话状态。
客户端需要存储以下会话状态:
- 已发送给服务端,但是还没有完成确认的 QoS 1 与 QoS 2 消息。
- 从服务端收到的,但是还没有完成确认的 QoS 2 消息。
服务端需要存储以下会话状态:
- 会话是否存在,即使会话状态其余部分为空。
- 客户端订阅信息,包括任何订阅标识符。
- 已发送给客户端,但是还没有完成确认的 QoS 1 与 QoS 2 消息。
- 等待传输给客户端的 QoS 0 消息(可选),QoS 1 与 QoS 2 消息。
- 从客户端收到的,但是还没有完成确认的 QoS 2 消息,遗嘱消息和遗嘱延时间隔。
- 会话过期时间。
会话状态的使用
如果客户端因为网络波动等原因导致连接短暂中断,但在会话过期前重新与服务端建立了连接,那么就可以沿用上次连接建立的订阅关系,不需要重新订阅一遍。在低带宽、不稳定的网络场景下,网络中断可能会发生得很频繁,保存会话状态的方式避免了每次连接都需要重新订阅,降低了重连时客户端和服务端的资源消耗。服务端在客户端脱机期间为其保留未完成确认的以及后续到达的消息,客户端重新连接时再一并转发,既可以避免消息丢失,也能够降低某些场景下用户对网络变化的感知度。
会话的开始与结束
MQTT v5.0 与 v3.1.1 在会话上有着较为显著的变化。MQTT v3.1.1 只有一个 Clean Session 字段,由客户端在连接时指定,为 1 表示客户端和服务器必须丢弃任何先前的会话并创建一个新的会话,且这个会话的生命周期与网络连接保持一致;为 0 则表示服务端必须使用与 Client ID 关联的会话来恢复与客户端的通信(除非会话不存在),客户端和服务器在断开连接后必须存储会话的状态。
MQTT v3.1.1 没有规定持久会话应该在什么时候过期,如果仅从协议层面理解的话,这个持久会话应该永久存在。但在实际场景中这并不现实,因为它会非常占用服务端的资源,所以服务端通常不会遵循协议来实现,而是向用户提供一个全局配置来限制会话过期时间。
而到了 MQTT 5.0,这个问题得到了妥善的解决,Clean Session 字段被拆分成了 Clean Start 字段与 Session Expiry Interval 字段。Clean Start 字段指定是否需要全新的会话,Session Expiry Interval 字段指定会话过期时间,它们在连接时指定,但 Session Expiry Interval 字段可以在客户端断开连接时被更新。因此我们可以很轻易地实现客户端网络连接异常断开时会话被保留,客户端正常下线时会话则随着连接关闭而终结的功能。
客户端如何知道这是被恢复的会话?
显而易见的是,当客户端以期望从先前建立的会话恢复状态的方式发起连接,它需要知道服务端是否存在相应的会话,才能决定在连接建立后是否需要重复一遍订阅操作。关于这一点,MQTT 协议从 v3.1.1 开始,就为 CONNACK 报文设计了 Session Present 字段,用于表示当前连接使用的是否是一个全新会话,客户端可以根据这个字段的值进行判断。
使用建议
开发者需要特别注意 ClientID 与会话之间的联系,如果某些场景下同一个 ClientID 会被不同的应用或者用户多次使用,即每次连接都会有完全不同的行为,那么就需要确保每次连接时都请求了全新的会话。合理地评估是否需要持久会话,如非必要可以在正常离线时将会话设置为立即过期减少服务端资源占用。设置合适的会话过期时间,设置过短,可能会失去存储会话状态的意义,设置过长,可能会过多地占用服务端资源。
版权声明: 本文为 EMQ 原创,转载请注明出处。