到目前为止,在第1部分和本系列的第2部分中,赫兹量化一直在处理MQTT协议的非操作部分的一小部分。我们在两个独立的头文件中组织了所有的协议定义、枚举和一些将在类之间共享的公共函数。此外,赫兹量化编写了一个接口作为对象层次结构的根,并在一个类中实现了它,其唯一目的是构建一个一致的MQTT CONNECT数据包。同时,我们一直在为构建数据包所涉及的每个函数编写单元测试。尽管我们将生成的数据包发送到本地MQTT代理,以检查它是否会被识别为格式良好的MQTT数据包,但从技术上讲,这一步骤是不需要的。由于我们使用固定数据来提供函数参数,所以我们知道我们是在孤立地测试它们,我们知道我们以独立于状态的方式测试它们。这很好,我们将努力继续以这种方式编写我们的测试,从而编写我们的函数。这将使我们的代码更加灵活,允许我们在不更改测试代码的情况下更改函数实现,只要我们有相同的函数签名。
从现在起,赫兹量化将处理MQTT协议的操作部分。毫不奇怪,它在OASIS标准中被命名为Operational Behavior(操作行为)。也就是说,从现在起,我们需要处理从服务器发送的数据包。我们的客户端必须能够识别服务器数据包类型及其语义,并且必须能够在给定的上下文中选择适当的行为,在每个可能的客户端状态中选择合适的行为。
为了能够处理此任务,我们必须在响应的第一个字节中识别服务器数据包类型。如果它是CONNACK数据包,我们必须读取其连接原因代码(Connect Reason Code),并做出相应的反应。
添加图片注释,不超过 140 字(可选)
(CONNECT)设置客户端连接标志
当我们的客户端请求与服务器连接时,它必须通知服务器
-
代理的一些期望的能力,
-
是否它将需要使用用户名和密码进行身份验证,
-
以及该连接是否是新会话,还是它正在恢复先前打开的会话。
这是通过在“Protocol Name(协议名称)”和“Protocol Version(协议版本)”之后的 Variable Header (变量头)的开头设置一些位标志来实现的。CONNECT 数据包上的这些位标志被命名为 CONNECT Flags (连接标志)。
请记住,位标志是布尔值。它们可能被赋予不同的名称或表示,但布尔值只有两个可能的值,通常是true或false。
添加图片注释,不超过 140 字(可选)
图01-用于表示布尔值的常用术语
OASIS标准一致使用1(一)和0(零)。我们将在这里大部分时间使用true和false,最终,我们将使用 set 和 unset。这将使文本更具可读性。此外,我们的公共API始终使用true和false来设置这些值,因此使用这些术语应该会使关注库开发的读者更容易理解本文。
添加图片注释,不超过 140 字(可选)
图02 - OASIS连接标志位
正如您在上图中的OASIS表中所看到的,第一个位(bit_0)是保留的,我们必须将其单独保留:置零、未检查、布尔值为false、未设置。如果我们设置它,我们将有一个格式不正确的数据包(Malformed Packet)。
Clean Start (bit_1)
赫兹量化软件可以设置的第一个位是第二个位。它用于设置“Clean Start”标志——如果为true,服务器将执行“干净启动(Clean Start)”并丢弃与我们的客户端标识符相关联的任何现有会话。服务器将启动一个新会话。如果未设置,服务器将恢复我们以前的会话(如果有),或者如果没有与我们的客户端标识符关联的现有会话,则启动新会话。
这就是我们现在设置/取消设置此标志的函数。
void CPktConnect::SetCleanStart(const bool cleanStart) { cleanStart ? m_connect_flags |= CLEAN_START : m_connect_flags &= ~CLEAN_START; ArrayFill(ByteArray, ArraySize(ByteArray) - 1, 1, m_connect_flags); }
我们通过逐位运算来切换值。我们使用三元运算符来切换布尔赋值和复合赋值,以使代码更加紧凑。然后我们将结果存储在m_connect_flags私有类成员中。最后,我们通过调用内置函数ArrayFill,用新值更新表示CONNECT数据包的字节数组。(请注意,使用数组填充的最后一步是我们稍后可能会更改的实现细节。)
我们的一个测试中的这一行显示了它是如何被调用的。
CPktConnect *cut = new CPktConnect(buf); //--- Act cut.SetCleanStart(true);
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
User Name Flag | Password Flag | Will Retain | Will QoS 2 | Will QoS 1 | Will Flag | Clean Start | Reserved | |
X | X | X | X | X | X | 1 | 0 |
表01-位标志Clean Start(bit_1)设置为true–MQTT v5.0
我们将广泛使用此模式来切换布尔标志:三元运算符和带复合赋值的逐位运算。
以下三个名为 Will ‘Something’ 的标志 旨在表达我们对服务器具有某些功能的愿望。他们通知服务器,我们“愿意”服务器能够
-
存储 Will 消息,并将其与我们的客户端会话相关联(稍后将对此进行详细介绍);
-
提供特定的QoS级别,通常高于QoS 0,如果没有设置,则为默认值;
-
保留 Will 信息,并在 Will 信息设置为true的情况下将其发布为“retained(保留)”(见下文)
Will Flag (bit_2)
第三位用于设置Will Flag –如果设置为true,我们的客户端必须提供“在网络连接未正常关闭的情况下”发布的Will 消息。人们可以将Will 消息视为一种客户在面对订阅者时死去后的“遗言”。
void CPktConnect::SetWillFlag(const bool willFlag) { willFlag ? m_connect_flags |= WILL_FLAG : m_connect_flags &= ~WILL_FLAG; ArrayFill(ByteArray, ArraySize(ByteArray) - 1, 1, m_connect_flags); }
它的调用方式与上一个函数相同。
//--- Act CPktConnect *cut = new CPktConnect(buf); cut.SetWillFlag(true);
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
User Name Flag | Password Flag | Will Retain | Will QoS 2 | Will QoS 1 | Will Flag | Clean Start | Reserved | |
X | X | X | X | X | 1 | X | 0 |
表02-位标志 Will Flag(bit_2)设置为true–MQTT v5.0
Will QoS (bit_3, bit_4)
与前面的两个标志不同,如果客户端请求QoS级别2,则此功能需要设置两个位,即第四和第五位。QoS代表服务质量(Quality of Service),可以是三者之一。
添加图片注释,不超过 140 字(可选)
图 03 - OASIS QoS 定义
从最不可靠到最可靠的交付系统,它们是:
QoS 0
QoS 0最多设置一次交付。这是一种“发射就结束”,发送人将尝试一次,消息可能会丢失。没有来自服务器的确认。这是默认值,也就是说,如果在位3和4上没有设置任何内容,则客户端请求的QoS级别为QoS 0。
QoS 1
QoS 1 设置至少传递一次。它有一个确认收到的 PUBACK。
相同的函数定义模式。
void CPktConnect::SetWillQoS_1(const bool willQoS_1) { willQoS_1 ? m_connect_flags |= WILL_QOS_1 : m_connect_flags &= ~WILL_QOS_1; ArrayFill(ByteArray, ArraySize(ByteArray) - 1, 1, m_connect_flags); }
相同的函数调用模式。