English Русский Español Deutsch 日本語 Português
preview
为 Metatrader 5 开发MQTT客户端:TDD方法——第4部分

为 Metatrader 5 开发MQTT客户端:TDD方法——第4部分

MetaTrader 5积分 | 22 五月 2024, 12:45
184 0
Jocimar Lopes
Jocimar Lopes

“软件实体对扩展来说应该是开放的,但对修改来说应该是封闭的。”(面向对象编程的开放-封闭原则)

概述

为了确保我们都了解情况,快速回顾一下可能会有所帮助。在本系列的第一部分中,我们开始为能够生成 MQTT 控制数据包固定头的函数编写初始测试。我们从第一个字节的第一位开始。如果有婴儿的一小步,那肯定就是这样的一小步。

第二部分中,我们在两个头文件中组织了一些共享函数和定义。

第三部分中,我们开始读取CONNACK的确认标志(Acknowledge Flags)和连接原因代码(Connect Reason Codes)。这是我们第一次接触标准的操作行为(Standard’s Operational Behavior)部分。 

在此之前,一切都是静态的,并与连接尝试绑定在一起。如果第一个字节是错误的,则会得到代理故障响应,并可能再次尝试连接到格式正确的数据包。 

属性(Properties)是另一回事,它们是MQTT应用程序消息的动态属性,可以在连接后更改。由于操作原因,代理商的最大QoS可能会临时更改。由于网络瓶颈,接收最大值可能已更改。除此之外,有些属性在设计上是动态的,如内容类型(Content-Type)、所有遗赠属性(Will Properties)和用户属性(User Property)。这些属性将可以一直更改。

OASIS标准在其规范中非常明确,但客户端开发人员仍有很大的空间来决定如何读取、反应、持久化和更新当前属性。顺便说一句,会话(Session)状态管理的持久层是客户端开发人员百分之百的责任。我们必须实现一个持久层来正确管理会话之间的属性。我们在这里的算法选择对一致性、稳健性和性能至关重要。

对于未来的库维护人员,“我们如何读取 MQL5中的MQTT v5.0 属性”一节中的注释可以用作非正式文档。一些库的最终用户也可能从这些注释中受益。在该部分中,我们将从库开发人员的角度来看这些属性。我们将处理它们的数据类型、标识符和字节数组上的位置,以更好地描述我们如何读取它们。下面,我们将从库的用户的角度来看这些属性。我们将尝试在每个可能的用例中描述它们的语义。

注:除非另有说明,否则所有引用均来自OASIS标准。 


MQTT v5.0中的属性是什么

属性是MQTT v5.0中添加的“可扩展性机制”的一部分。它们在之前的v3.1.1中不存在,这是此次重大升级之前的最新版本。它们在MQTT v5.0中无处不在。但是,什么是MQTT属性?究竟是什么东西的属性呢?

答案是应用程序消息(Application Message)的属性。在OASIS标准的术语中,应用程序消息是

“MQTT协议为应用程序在网络上携带的数据。当应用程序消息由MQTT传输时,它包含有效负载数据、服务质量(QoS)、属性集合和主题名称。”(重点是我们加上的)

请看一下下面图1中表示“有效负载数据”的黄色矩形。这里有一个重要的术语区别,我们想提请您注意。

MQTT 5.0应用程序消息摘要图

图01 - MQTT 5.0应用程序消息摘要图

在消息共享协议的上下文中,当我们看到“消息”一词时,我们习惯于思考用户消息,通常是文本消息。通常情况下,我们不会将消息视为整个应用程序。 

但在这里,用户通过MQTT发送的消息是有效负载数据的一部分,属性是名为ApplicationMessage的协议抽象模型的一部分。因此,当我们通过MQTT发送用户消息时,我们不仅可以具有与“用户消息”相关的属性,而且还可以具有与整个应用程序消息相关的属性:连接的属性、发布的属性、订阅和取消订阅主题的属性、身份验证的属性等等。 <段3925>

除此之外,Will 信息还附有 Will 属性。

“遗赠信息由CONNECT有效载荷中的遗赠属性、遗赠主题和遗赠有效载荷字段组成。"

当一个人开始实现协议时,这个术语可能有点令人困惑,但我们会尽最大努力使其尽可能清晰。


属性的用途是什么?

除了携带有效载荷元数据外,属性还可用于配置客户端和服务器(代理)之间交互的各个方面,以及不同客户端之间的交互。从连接到断开连接,它们可用于设置内容类型格式、向代理请求信息、定义消息过期时间跨度、选择身份验证方法,甚至执行服务器重定向等用例。我们可以在下表中看到,除了用于刷新 Keep Alive 周期的 PINGREQ 和 PINGRESP 数据包外,所有数据包类型都可以根据数据包上下文携带一些特定的属性。

用户属性是可以在所有数据包中使用的属性的特殊情况,其含义由应用程序定义,这意味着其语义不由协议定义。我们将在最后一节中快速介绍用户属性,讨论如何使用属性来扩展协议。

尽管属性名称的用途很明确,但我们需要知道:

  • 何时可以使用
  • 何时必须使用
  • 如果设置错误会发生什么

为了便于阅读和理解以下描述,在下表中,我们根据其功能将其分组为不同的颜色。请注意,分组在某种程度上是任意的,因为属性的使用在不同的数据包类型之间重叠。

在下面的描述中,我们使用了术语 MUST和 MAY,因为它们被OASIS标准使用,而OASIS标准是按照IETF RFC 2119中所述使用它们的。


连接属性(Connection Properties)
CONNECT Session Expiry Interval(会话到期间隔),Receive Maximum(接收最大值),Maximum Packet Size(最大数据包大小), Topic Alias Maximum(主题别名最大值), Request Response Information(请求响应信息), Request Problem Information(请求问题信息), User Property(用户属性), Authentication Method(身份验证方法), Authentication Data(身份验证数据)
CONNECT Payload Will Delay Interval(遗赠延迟间隙), Payload Format Indicator(有效载荷格式指示器), Message Expiry Interval(消息过期间隔), Content Type(内容类型), Response Topic(回应主题), Correlation Data(相关性数据), User Property(用户属性)
CONNACK Session Expiry Interval(会话到期间隔), Receive Maximum(接收最大值), Maximum QoS(Qos最大值), Retain Available(保留可用), Maximum Packet Size(最大数据包大小), Assigned Client Identifier(分配的客户端标识符), Topic Alias Maximum(主题别名最大值), Reason String(原因字符串), User Property(用户属性), Wildcard Subscription Available(可用的通配符订阅), Subscriptions Identifiers Available(可用的订阅标识符), Shared Subscription Available(共享订阅可用), Server Keep Alive(服务器保持活动), Response Information(回应信息), Server Reference(服务器参考), Authentication Method(身份验证方法), Authentication Data(身份验证数据)
DISCONNECT Session Expiry Interval(会话到期间隔), Reason String(原因字符串), User Property(用户属性), Server Reference(服务器参考)

表1:MQTT v5.0-按功能分组的属性-连接属性

Assigned Client Identifier - 必须在CONNECT上设置。如果未设置,则代理可以在CONNACK上分配标识符。

CONNECT数据包上必须有客户端标识符。但是,允许代理接受长度为零字节的标识符,并将标识符分配给客户端。在这种情况下,代理将在该属性下的CONNACK数据包中返回它。

Maximum Packet Size – 可以不设置,但不能设置为零。

最大数据包大小“是MQTT控制数据包中的总字节数”。客户端和代理使用它来定义它们愿意接受的最大数据包大小。如果我们的客户端设置了此属性,并且代理发送的数据包大于此限制,我们必须断开连接,原因代码为0x95(数据包太大)。如果包含原因字符串会使数据包大于此属性,则代理不会发送原因字符串或某些用户属性。

Maximum QoS – 由CONNACK上的代理使用,可能未设置

最大QoS向客户端通知代理处理QoS级别的能力。如果代理接受QoS级别2,则不会设置此属性。我们的客户端必须遵守此服务器限制,不发送具有更高QoS级别的PUBLISH数据包。我们被允许在客户端中仅支持QoS级别0,并且仍然是一致的。

非规范性意见

“客户端不需要支持QoS 1或QoS 2 PUBLISH数据包。如果是这种情况,客户端只需将其发送的任何SUBSCRIBE命令中的最大QoS字段限制为其可以支持的值。"

Message Expiry Interval – 可以不设置。

消息到期时间间隔也是PUBLISH属性的一部分。它设置遗赠消息的生存期。如果未设置,则当代理发布遗赠消息时,它将没有定义的过期时间。

“如果消息过期时间间隔已过,而服务器未能启动向匹配的订阅服务器的转发,则它必须删除该订阅服务器的消息副本”。

它也可以在 CONNECT Payload 的遗赠属性上设置。如果未设置,则消息不会过期。

Payload Format Indicator – 如果我们发送字符数据,则必须设置。

有效负载格式指示器可以在CONNECT有效负载的遗赠属性上设置。这里它指示Will Message是UTF-8编码的字符数据还是“未指定字节”。 

“服务器可以验证遗赠消息是否为所指示的格式,如果不是发送原因代码为0x99的CONNACK(有效负载格式无效)”

它是PUBLISH属性的一部分,表示有效载荷的格式。代理验证也是可选的。但是,如果它验证了有效负载格式,那么如果格式与设置中的不同,我们就可能得到具有相同原因代码(0x99)的PUBACK、PUBREC或DISCONNECT。

如果未设置,则假定Payload格式为“未指定字节”。也就是说,如果我们正在发送字符数据,则必须设置此属性。

Reason String – 它可以由客户端或代理在所有ACK、DISCONNECT和AUTH上使用。

原因字符串是MQTT v5.0中的一个新特性。它可以用来用人类可读的诊断工具补充原因代码。例如,如果存在,它可以用于日志记录。我们可以通过在CONNECT属性上将“请求问题信息”设置为零来请求代理不发送它。 

Receive Maximum - 可以不设置,但是不能设置为零。 

我们的客户端可以在CONNECT上使用此属性来限制我们愿意在当前网络连接上同时处理的QoS 1和QoS 2发布的数量。代理可以在CONNACK上设置它,如果没有设置,则默认为 65,535。

在QoS 0上,不需要等待PUBACK(QoS 1)或PUBCOMP(QoS 2),因为正如我们所知,QoS 0是“激发并忘记”的。此属性在接收相应的PUBACK或PUBCOMP之前,设置我们的客户端或代理愿意发送/接收的消息数量。我们可以将其视为一种手段,说明在发送新消息之前,有多少消息可以处于“待确认”状态。

Request Problem Information – 可以在 CONNECT上设置。

请求问题信息用于通知服务器,在发生故障时,我们希望接收原因字符串和用户属性。如果我们什么也不说——通过不设置它——那么代理可以发送它们。

Request Response Information – 可以在 CONNECT 上设置。

请求-响应信息是通过MQTT进行的请求/响应交互的一部分,而不是常规的发布/订阅交互。如果未设置,我们会通知代理我们不希望它发送响应信息。否则,设置为允许代理向我们发送该响应。请注意,即使我们请求,代理也可以不发送响应信息。如果没有此属性,则值默认为unset。

Response Information – 可以在 CONNECT 上设置。

响应信息是通过MQTT进行的请求/响应交互的一部分,而不是常规的发布/订阅交互。 

非规范性意见

“它的一个常见用途是传递主题树的全局唯一部分,该部分至少在其会话的生存期内保留给该客户端。这通常不能只是一个随机名称,因为请求客户端和响应客户端都需要获得授权才能使用它。将其用作特定客户端的主题树的根是正常的。服务器要返回此信息,通常需要对其进行正确配置。使用此机制可以在服务器中而不是在每个客户端中完成一次此配置。"

Response Topic – 可以在 CONNECT 或 PUBLISH 上设置。 

响应主题是通过MQTT进行的请求/响应交互的一部分,而不是常规的发布/订阅交互。如果我们将其包括在内,则代理将遗赠消息解释为请求。与SUBSCRIBE数据包上使用的主题筛选器不同,响应主题不能具有通配符。

“请求消息是具有响应主题的应用程序消息。”

因此,这是将应用程序消息表征为请求/响应交互的一部分的属性。

Retain Available – 可以在 CONNACK 上预先设置。

保留可用属性通知我们的客户端,代理是否支持保留消息,如果不存在,则保留消息可用。

Server Keep Alive – 可以在 CONNACK 上预先设置。

服务器保持活动属性优先于CONNECT上请求的客户端保持活动属性。如果CONNACK上没有此属性,那么我们可以使用我们自己的Keep Alive。否则,服务器保持活动规则。

Server Reference – 可以在 CONNACK 或 DISCONNECT 上预先设置。

服务器参考向我们的客户端通知服务器重定向。它可以指临时或永久重定向。在这两种情况下,我们的客户端可能已经知道另一个服务器,或者将使用此属性指定它。

非规范性意见

服务器参考的示例有:

myserver.xyz.org

myserver.xyz.org:8883

10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883

代理被允许永远不发送此属性,我们的客户也被允许忽略它。

Session Expiry Interval – 可以在 CONNECT 上设置。

会话到期时间间隔决定断开连接后会话的保留时间。如果未设置或不存在,会话将在连接关闭时结束。通过将此属性设置为UINT_MAX,可以将会话设置为不过期。如果此属性大于零,则必须存储会话状态。我们可以在CONNACK上的 Session Present 标志上进行检查。

当网络连接是间歇性的时,此属性非常有用,允许我们的客户端在网络连接恢复时恢复会话。

Shared Subscription Available – 可以在 CONNACK 上预设置。

如果代理支持共享订阅,可用共享订阅会通知我们的客户。如果不设置,则代理支持。

Subscription Identifiers Available – 可以在 CONNACK 上预设。

如果经纪人支持订阅标识符,可用的订阅标识符会通知我们的客户。如果不设置,则代理支持。

Topic Alias Maximum – 可以在 CONNECT 上设置,也可以在 CONNACK 上预设。

最大主题别名会通知代理我们的客户在此特定连接上愿意接受的主题别名的最大数量。如果我们将其设置为零或留空,则代理将不会在此连接中发送任何主题别名。反之亦然:如果CONNACK上不存在此属性,或者存在此属性但其值为零,则我们的客户端不得发送任何主题别名。

Wildcard Subscription Available – 可以在 CONNACK 上预设。

如果此属性未设置(设置为零),则代理不支持通配符订阅。在这种情况下,代理将在收到请求通配符订阅的SUBSCRIBE后断开连接。但是,即使代理支持该功能,它也可以拒绝包含通配符订阅的特定订阅请求,并返回具有相同原因代码0xA2的SUBACK(不支持通配符订阅)。如果CONNACK中没有该属性,则代理支持该功能。

Will Delay Interval – 可以在CONNECT有效负载的Will Properties上设置。

此属性设置代理在发送遗赠消息之前要观察到的延迟(以秒为单位)。此属性对于避免在不稳定或间歇性网络连接下发送遗赠消息特别有用。


Publishing Properties(发布属性)
PUBLISH Payload Format Indicator(有效载荷格式指示器), Message Expiry Interval(消息过期间隙), Topic Alias(主题别名), Response Topic(响应主题), Correlation Data(相关性数据), User Property(用户属性), Subscription Identifier(订阅标识符), Content Type(内容类型)
PUBACK Reason String(原因字符串), User Property(用户属性)
PUBREC Reason String(原因字符串), User Property(用户属性)
PUBREL Reason String(原因字符串), User Property(用户属性)
PUBCOMP Reason String(原因字符串), User Property(用户属性)

表2:MQTT v5.0-按功能分组的属性-发布属性

Topic Alias – 可以在 PUBLISH 上设置

主题别名也是MQTT v5.0的一个新功能。它允许代理或客户端通过将Topic Name(主题名称)替换为一个小整数(别名)来减小数据包的大小。由于主题名称是可以扩展到65535字节(UINT_MAX)的字符串,因此开销的减少可以是有表现力的。

Correlation Data – 可以在PUBLISH和Will属性上设置

相关性数据是通过MQTT进行的请求/响应交互的一部分,而不是常规的发布/订阅交互。它的值只对应用程序(代理和客户端)有意义。它是请求/响应中使用的二进制数据,“由请求消息的发送者在接收到响应消息时标识该响应消息用于哪个请求。”

Content Type – 可以在PUBLISH和Will属性上设置

内容类型也可以在CONNECT中用于设置遗赠消息内容类型。 

代理仅验证属性本身的编码。这个属性的含义由客户端决定。


Subscribing/Unsubscribing Properties(订阅/取消订阅属性)
SUBSCRIBE Subscription Identifier(订阅标识符), User Property(用户属性)
SUBACK Reason String(原因字符串), User Property(用户属性)
UNSUBSCRIBE User Property(用户属性)
UNSUBACK  Reason String(原因字符串), User Property(用户属性)

表3:MQTT v5.0 - 按功能分组的属性-订阅/取消订阅属性

Subscription Identifier – 可以在SUBSCRIBE上设置

这是一个可以在SUBSCRIBE上设置的数字标识符。它将由代理在消息上返回,允许客户端确定是哪个订阅导致了消息的传递。它的值可以是1到268435455。不得设置为零,并且不得在从客户端到服务器的PUBLISH中使用。


Authentication Properties(身份验证属性)
AUTH
Authentication Method(身份验证方法), Authentication Data(身份验证数据), Reason String(原因字符串), User Property(用户属性)

表4:MQTT v5.0 - 按功能分组的属性-身份验证属性

在没有意外的情况下,这些特性也可以用于连接。

Authentication Method

除了使用用户名和密码的基本网络身份验证外,MQTT v5.0还允许“Extended Authentication(扩展身份验证)”。此属性通知选择的方法。选择的方法由应用程序开发人员定义。代理将通知是否支持该方法。

身份验证方法通常是SASL机制,使用这样的注册名称有助于交换。但是,身份验证方法不限于使用已注册的SASL机制

Authentication Data

此属性由客户端和代理根据所选的身份验证方法来交换身份验证数据。


我们如何在MQL5中读取MQTT v5.0属性

到目前为止,在本系列的前几部分中,我们一直在处理通过位标志配置的“每个会话”设置,即Connect上的连接标志、CONNACK原因代码和CONNACK会话存在标志。这些设置在每个会话中读取/写入/持久化一次。但随着属性的不同,情况就不同了。它们是应用程序消息的一部分,在某些应用程序配置文件中可能携带大量关键数据。因此,我们的客户必须随时准备好读取和写入属性。

为了编写读取服务器发送的属性的测试,我们需要一个示例字节数组。我们将从CONNACK数据包的样本字节数组开始,因为这是我们的客户端将处理的第一个数据包。与所有MQTT控制数据包一样,它有一个两字节的固定头和一个两个字节的可变头,其中一个字节用于连接确认标志,另一个字节用作连接原因代码。属性是CONNACK数据包中的最后一个字段,它没有数据包标识符,也没有有效载荷。

MQTT 5.0-CONNACK数据包的结构

图02:MQTT 5.0 CONNACK数据包的结构

从标准中,我们知道:

“属性集由属性长度和属性组成。”

我们还知道:

“属性长度编码为可变字节整数。属性长度不包括用于对自身进行编码的字节,但包括属性的长度。如果没有属性,则必须通过包含属性长度为零来表示。”

因此,固定头剩余长度和属性长度都编码为可变字节整数,是我们在访问属性之前需要读取的第一条信息。如果“属性长度”为零,则没有可读取的内容。

因此,对于没有属性的CONNACK,我们的示例字节数组可能如下所示:

uchar connack_response[] = {2, X, 0, 0, 0};

其中,X是固定标头的剩余长度。该标准提供了对可变字节整数进行解码的算法。在MQL5中,此算法可以这样写:

uint DecodeVariableByteInteger(uint &buf[], uint idx)
  {
   uint multiplier = 1;
   uint value = 0;
   uint encodedByte;
   do
     {
      encodedByte = buf[idx];
      value += (encodedByte & 127) * multiplier;
      if(multiplier > 128 * 128 * 128)
        {
         Print("Error(Malformed Variable Byte Integer)");
         return -1;
        }
      multiplier *= 128;
     }
   while((encodedByte & 128) != 0);
   return value;
  };

其中buf[idx]表示“流中的下一个字节”。

尽管标准提供了解码可变字节整数的算法,但我们也为其编写了一个非常简单的测试,只是为了确保实现在现阶段按预期工作:

bool TEST_DecodeVariableByteInteger()
  {
   Print(__FUNCTION__);
   uint buf[] = {1, 127, 0, 0, 0};
   uint expected = 127;
   uint result = DecodeVariableByteInteger(buf, 1);
   ZeroMemory(buf);
   return AssertEqual(expected, result);
  }

显然,出于测试目的,剩余长度值将被硬编码。对于上述没有任何属性的CONNACK,它将是:

uchar connack_response[] = {2, 3, 0, 0, 0};

CONNACK的一个示例字节数组的一个字节Payload Format Indicator属性设置为UTF-8编码字符串有效载荷格式,它可能类似于:

uchar connack_response_one_byte_property = {2, 5, 0, 0, 2, 1, 1};

正如您所看到的,检查CONNACK中属性的存在非常简单。我们只需要读取包含属性长度的第五个字节。如果它是非零的,我们就有属性。我们的第一个测试如下:

bool TestProtectedMethods::TEST_HasProperties_CONNACK_No_Props()
  {
   Print(__FUNCTION__);
//--- Arrange
   bool expected = false;
   uchar connack_no_props[5] = {2, 3, 0, 0, 0};
//--- Act
   CSrvResponse *cut = new CSrvResponse();
   bool result =  this.HasProperties(connack_no_props);
//--- Assert
   bool isTrue = AssertEqual(expected, result);
//--- cleanup
   delete cut;
   ZeroMemory(result);
   return  isTrue ? true : false;
  }

请参阅我们上一篇文章,了解我们是如何测试受保护的方法的,还请参阅所附代码,了解测试的FAIL变体。

第一个实现,刚好可以通过当前阶段的测试,看起来是这样的:

bool CSrvResponse::HasProperties(uchar &resp_buf[])
  {
   return resp_buf[4] != 0 ? true : false;
  }

这里,我们使用三元运算符来将代码保持在最小值。

请注意,我们必须考虑到属性长度字节的位置取决于数据包类型。这是因为,尽管属性总是变量头中的最后一个字段,但有些数据包需要在属性长度之前有一个两字节的数据包标识符。在CONNACK中,这不是问题。

“那么这个代码将不适用于其他数据包类型!”,你可能会说。是的,你是对的。但是,请记住,我们在这里使用的是TDD方法。这种做法的主要好处之一是让我们专注于手头的任务,而不是在开发的第一阶段试图处理所有可能的未来问题当时间到了,我们的测试失败时,我们将处理其他数据包类型。然后,我们将重写测试,最终重构代码。

尽管这看起来有点违背直觉,但一旦习惯了,就不能像以前那样编写代码了。如果不是因为其他原因,因为这会让我们的工作更轻松,甚至是一种乐趣。顺便说一句,在本系列的下一部分中,我们将开始编写和读取PUBLISH数据包的属性。请继续关注!

如果属性长度为非零,我们可以在下一个字节中查找属性标识符。属性标识符为我们提供了属性数据类型。

“属性由一个标识符组成,该标识符定义其用法和数据类型,后跟一个值。”

uchar CSrvResponse::GetPropertyIdentifier(uchar &resp_buf[])
  {
   return resp_buf[5];
  }

数据类型为我们提供了要读取的字节数。数据类型可以是以下类型之一:

单字节整数

uchar CSrvResponse::ReadOneByteProperty(uchar &resp_buf[])
  {
   return resp_buf[6];
  }

两字节整数

void CSrvResponse::ReadTwoByteProperty(uchar &resp_buf[], uchar &dest_buf[])
  {
   ArrayCopy(dest_buf, resp_buf, 0, 6, 2);
  }

四字节整数

void CSrvResponse::ReadFourByteProperty(uchar &resp_buf[], uchar &dest_buf[])
  {
   ArrayCopy(dest_buf, resp_buf, 0, 6, 4);
  }

可变字节整数(仅适用于订阅标识符) 

void CSrvResponse::ReadVariableByteProperty(uint &resp_buf[], uint &dest_buf[], uint start_idx)
  {
   uint value = DecodeVariableByteInteger(resp_buf, start_idx);
   ArrayResize(dest_buf,value,7);
   ArrayFill(dest_buf, 0, 1, value);
  }

此属性的读取/解码(以及写入/编码)所需的内容远不止此测试目前所检查的内容。

“可变字节整数使用一种编码方案进行编码,该编码方案使用单个字节来表示最大127的值。较大的值按如下方式处理。每个字节的最低有效七位对数据进行编码,最高有效位用于指示表示中是否有字节跟随。因此,每个字节编码128个值和一个“延续位'”(重点是我们加上的)

在实现SUBSCRIBE数据包时,我们将处理可变字节整数属性,因为它仅用于Subscription Identifier属性。还有以下三种数据类型:UTF-8编码的字符串、二进制数据和UTF-8字符串对。它们将在请求/响应的使用和用户属性特殊情况的实现的上下文中进行详细说明。

UTF-8编码的字符串以其长度为前缀。

“这些字符串中的每一个都以一个双字节整数长度字段为前缀,该字段给出UTF-8编码字符串本身的字节数,如下图1.1 UTF-8编码字符串的结构所示。因此,UTF-8编码字符串的最大大小为65535字节。除非另有说明,否则所有UTF-8编码字符串的长度都可以在0到65535字节之间。”

MQTT-v5-utf8-encoded-strings-structure-OASIS

图03:MQT 5.0-UTF-8编码字符串的结构-OASIS表的屏幕截图

值得注意的是,必须验证UTF-8编码的字符串是否存在不允许的Unicode代码点。(稍后会详细介绍)

“第1.6.4节描述了不允许的Unicode代码点,这些代码点不应包含在UTF-8编码字符串中。客户端或服务器实现可以选择是否验证这些代码点未在UTF-8编码的字符串(如主题名或属性)中使用。"

二进制数据也以其长度为前缀。

“二进制数据由两字节整数长度表示,该长度表示数据字节数,后跟该字节数。因此,二进制数据的长度被限制在0到65535字节的范围内。”

我们一直在计算读取的字节数,这样我们就可以知道何时读取了所有属性。我们不需要担心属性的顺序。

“具有不同标识符的属性的顺序没有意义。”


如何使用属性来扩展协议

如本文顶部所述,属性是MQTT 5.0的“可扩展性机制”的一部分,该机制最突出的属性是可在任何MQTT控制数据包中使用的User属性。用户属性是键值对,其含义对协议来说是不透明的。也就是说,它的含义是由应用程序定义的。

让我们想象一下我们领域的一个用例:一个接收器正在复制来自三个不同提供商的交易信号,每个提供商都使用不同的代理。每个经纪商可能会为同一资产(比如黄金)分配不同的符号名称。

  • 经纪商A使用GOLD
  • 经纪商B使用XAUUSD
  • 经纪商C使用XAUUSD.s

除此之外,每个信号提供者可以使用多个经纪商。因此,signal_provider : provider_broker对可能随时发生变化,即使在交易时段进行时也是如此。(是的,我们这里有一个准组合爆炸。)接收者需要知道,理想情况下以毫秒为单位,它所接收的交易品种名称的含义,才能将其转换为其经纪人用来正确复制交易请求的交易品种名称。

如果没有用户属性,就像以前版本的协议一样,元数据(signal_provider:provider_broker)将不得不嵌入有效载荷中,在有效载荷中只能找到(和解析)所需的交易信号数据。

相比之下,如果每个信号提供程序都有自己的用户属性及其代理名称,则有效负载只能具有所需的信号数据。

这是这个用例的一个简单示例。但请记住,此元数据可以扩展到任何关键信息,包括JSON/XML字符串,甚至整个文件。因此,从某种意义上说,这种可能性是无限的。


结论

在本系列的第四部分中,我们简要介绍了MQTT v5.0中的属性、它们的语义以及一些用例。我们还报告了我们如何在CONNACK中实现它们,并提供了一个如何使用它们来扩展协议的简单示例。在下一部分中,我们将在PUBLISH数据包的上下文中应用它们,始终使用TDD方法来处理规范的复杂性。

如果您认为您可以为本机MQL5客户端的开发做出贡献,该客户端将成为我们代码库的一部分,请在下面的评论或我们的聊天中留言。欢迎任何帮助!:)


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/13651

附加的文件 |
群体优化算法:混合蛙跳算法(SFL) 群体优化算法:混合蛙跳算法(SFL)
本文详细描述了混合蛙跳(Shuffled Frog-Leaping,SFL)算法及其在求解优化问题中的能力。SFL算法的灵感来源于青蛙在自然环境中的行为,为函数优化提供了一种新的方法。SFL算法是一种高效灵活的工具,能够处理各种数据类型并实现最佳解决方案。
神经网络变得简单(第 58 部分):决策转换器(DT) 神经网络变得简单(第 58 部分):决策转换器(DT)
我们继续探索强化学习方法。在本文中,我将专注于一种略有不同的算法,其参考智能体政策构造一连串动作的范式。
神经网络变得简单(第 59 部分):控制二分法(DoC) 神经网络变得简单(第 59 部分):控制二分法(DoC)
在上一篇文章中,我们领略了决策变换器。但是,外汇市场复杂的随机环境不允许我们充分发挥所提议方法的潜能。在本文中,我将讲述一种算法,旨在提高在随机环境中的性能。
机器学习中的量化(第1部分):使用 CatBoost 的理论、示例代码和实现分析 机器学习中的量化(第1部分):使用 CatBoost 的理论、示例代码和实现分析
本文探讨了量化在树模型构建中的理论应用,并展示了使用 CatBoost 实现的量化方法。不使用复杂的数学方程。