client 在可以发布和订阅消息之前,必须先连接到 broker,下面我们来看一下连接到 broker 的流程。

1 Client 连接到 Broker 的流程

MQTT 系列之  MQTT broker 的连接

2 CONNECT

连接的建立由 Client 端发起,Client 端首先向 broker 发送一个 CONNECT 数据包,CONNECT 数据包包含以下内容(这里我们略过 fixed header)。

2.1 可变头(Variable header)

在 CONNECT 数据包可变头中,含有以下信息。

协议名称(Protocol name):值固定为 MQTT 字符。

协议版本:对于 MQTT3.1.1 来说,值为 4.

用户名标识:消息体中是否有用户名字段, 1bit , 0 或 1。

密码标识:消息体中是否有密码字段,1bit,0 或 1。

遗愿消息 Retain 标志(will retain):标识遗愿消息是否是 retain 消息,1 bit,0 或 1。

遗愿消息 QoS 标志 (will QoS):标志遗愿消息的 QoS 是否存在,1 bit,0 或 1。

会话清除标志(clean session):标识 Client 是否建立一个持久化的会话,1bit,0或1,当 clean session 的标识为 0 时,代表 client 希望建立一个之久的会话连接,broker 间存储该 client 订阅的主题和未接受的消息,否则broker不会存储这些数据,同时在建立连接时清除这个 client 之前的持久化会话保存的数据。

心跳保持(keep alive):设置一个单位为秒的时间间隔,client 和 broker 之间在这个时间内至少需要进行一次消息交互,否则 client 和 broker 会认为它们之间的连接已断开。

2.2 消息体(payload)

CONNECT 数据包的消息体中包含了以下数据。

客户端标识符(client identifier):Client Identifier 是用来标识 Client 身份的字段,在 MQTT3.1.1 的版本中,这个字段的长度是 1 到 23个字节,而且只能包含数字和26个字母(包括大小写),broker 通过这个字段来区分不同的 client。所有在连接的时候,client应该保证它的 identifier 是唯一的,通常我们可以使用比如 UUID,唯一的设备硬件标识,或者 Android 设备的 DEVICE_ID 等作为 Client identifier 的取值来源。MQTT 协议中要求 Client 连接时必须带上 Client Identifier,但是也允许 broker 在实现时 Client Identifier 为空,这时 Broker 会为 Client 分配一个内部唯一的 Identifier。如果你需要使用持久化会话,那就必须自己为 Client 设定一个唯一的 Identifier。

用户名(Username):如果可变头中的用户标识设为 1,那么消息体中将包含用户名字段,Broker 可以使用用户名和密码来对接入的 Client 进行验证,只允许已经授权的 Client 接入。注意不同的 Client 需要使用不同的 Client Identifier,但它们可以使用同样的用户名和密码进行连接。

密码(password):如果可变头中的密码标识为1,那么消息体中将包含密码字段。

遗愿主题(will topic):如果可变头中遗愿标识设为1,那么消息体中将包遗愿主题,当 Client 非正常地中断连接的时候,Broker将向指定的遗愿主题中发布遗愿消息。

遗愿消息(will message):如果可变头中的遗愿标志为1,那么消息体中将包含遗愿消息,当 Client 非正常地中断连接的时候,Broker 将向指定的遗愿主题中发布由该字段指定的内容。

3 CONNACK

当 broker 收到 client 的 CONNECT 数据包之后,将检查并校验 CONNECT 数据包的内容,之后回复 Client 一个 CONNACK 数据包。
CONNACK 数据包包含以下内容(这里略过 Fixed header)。

3.1 可变头

CONNACK 数据包的可变头中,含有如下信息:
会话存在标识(Session Present Flag):用于标识在 Broker 上,是否存在该 client(用 client identifier 区分)的持久性会话,1bit,0或1。当 Client 在连接时设置 clean session = 1,则 CONNACK 中的 Session Present Flag 始终为 0;当 client 在连接时设置 clean session = 0 时,那么分为两种情况:如果 broker 上面保存了这个 Client 之前留下的持久性会话,那么 CONNACK 中的 session present flag 值为 1,如果 broker 没有保存该 client 的任何会话数据,那么 CONNACK 中 session present flag 值为 0.

连接返回码:用于标识 client 是否连接建立成功,连接返回码如下:

return code 连接状态
0 连接已建立
1 连接失败,不允许的协议版本
2 连接失败, client identifier 被拒绝
3 连接失败, 服务器不可用
4 连接失败, 错误的用户名或密码
5 连接被拒绝, 未授权

在这里强调一下 code 4 和 5。Return Code 4 在 MQTT 协议中的含义是 Username 和 Password 的格式不正确,但是在大部分的 Broker 实现中,在使用错误的用户名密码时,得到的返回码也是 4。所以这里我们认为 4 就是代表错误的用户名或密码。Return Code 5 一般在 Broker 不使用用户名和密码而使用 IP 地址或者 Client Identifier 进行验证的时候使用,来标识 Client 没有通过验证。

3.2 payload

CONNACK 没有 payload。

4 断开连接(DISCONNECT)

4.1 Client 主动断开连接

Client 主动关闭连接的流程很简单,只需要向 broker 发送一个 DISCONNECT 数据包就可以了。DISCONNECT 数据包没有可变头和消息体。在 Client 发送完 DISCONNECT 后,无需等待 broker 的回复(broker 也不会有回复),直接关闭底层的 tcp 连接即可。

注意:当 Broker 收到 Client 的 DISCONNECT 数据包的时候,它认为 Client 是正常地断开连接,那么它会丢弃当前连接指定的遗愿消息(Will Message)。如果 Broker 检测到 Client 连接丢失,但又没有收到 DISCONNECT 消息包,它会认为 Client 是非正常断开连接,就会向在连接的时候指定的遗愿主题(Will Topic)发布遗愿消息(Will Message)。

4.2 Broker 主动关闭连接

MQTT 协议规定 Broker 在没有收到 Client 的 DISCONNECT 数据包之前都应该保持和 Client 连接,只有 Broker 在 Keep Alive 的时间间隔里,没有收到 Client 的任何 MQTT 数据包的时候会主动关闭连接。一些 Broker 的实现在 MQTT 协议上做了一些拓展,支持 Client 的连接管理,可以主动地断开和某个 Client 的连接。

Broker 主动关闭连接之前不会向 Client 发送任何 MQTT 数据包,直接关闭底层的 TCP 连接就完事了。