IoT 中心的 MQTT 5 支持(已弃用)

版本:2.0 api-version:2020-10-01-preview

本文档阐释了基于 MQTT 5.0 版协议的 IoT 中心数据平面 API。 有关此 API 中的完整定义,请参阅 API 参考

注释

IoT 中心的 MQTT 5 支持已弃用,IoT 中心对 MQTT 的功能支持有限。 如果你的解决方案需要 MQTT v3.1.1 或 v5 支持,我们建议查看 Azure 事件网格中的 MQTT 支持。 有关详细信息,请参阅比较 IoT 中心和事件网格中的 MQTT 支持

先决条件

  • 创建全新的 IoT 中心,并启用预览模式。 MQTT 5 仅在预览模式下可用,不能将现有 IoT 中心切换为预览模式。 有关详细信息,请参阅启用预览模式
  • 事先了解 MQTT 5 规范

支持级别和限制

对 MQTT 5 的 IoT 中心支持处于预览阶段,在以下方面受到限制(通过 CONNACK 属性传达给客户端,除非明确说明):

  • 目前还没有官方的 Azure IoT 设备 SDK 支持。
  • 不支持订阅标识符。
  • 不支持共享订阅。
  • 不支持 RETAIN
  • Maximum QoS1
  • Maximum Packet Size256 KiB(需遵守每个操作的进一步限制)。
  • 不支持分配的客户端 ID。
  • Keep Alive 仅限 19 min(活动性检查的最大延迟时间 - 28.5 min)。
  • Topic Alias Maximum10
  • Response Information 不受支持;即使 CONNACK 包含 Response Information 属性,CONNECT 也不会返回 Request Response Information 属性。
  • Receive Maximum(在满足 PUBLISH 条件时,客户端到服务器方向允许的最大未确认 QoS: 1 数据包数)为 16
  • 单个客户端的订阅数不能超出 50 个。 如果客户端达到订阅限制,SUBACK 会为订阅返回 0x97(超出配额)原因代码。

连接生命周期

连接

若要使用此 API 将客户端连接到 IoT 中心,请按 MQTT 5 规范建立连接。 在成功进行 TLS 握手后,客户端必须在 30 秒内发送 CONNECT 数据包,否则服务器会关闭连接。 下面是 CONNECT 数据包的示例:

-> CONNECT
    Protocol_Version: 5
    Clean_Start: 0
    Client_Id: D1
    Authentication_Method: SAS
    Authentication_Data: {SAS bytes}
    api-version: 2020-10-10
    host: abc.azure-devices.net
    sas-at: 1600987795320
    sas-expiry: 1600987195320
    client-agent: artisan;Linux
  • Authentication Method 属性是必需的,用于标识所使用的身份验证方法。 有关身份验证方法的详细信息,请参阅身份验证
  • Authentication Data 属性处理取决于 Authentication Method。 如果 Authentication Method 设置为 SAS,则 Authentication Data 是必需的,其中必须包含有效的签名。 有关身份验证数据的详细信息,请参阅身份验证
  • api-version 属性是必需的,必须将其设置为此规范的标头中提供的 API 版本值,才能使此规范得以应用。
  • host 属性定义租户的主机名。 除非在 TLS 握手期间的客户端 Hello 消息中提供了 SNI 扩展,否则这是必需的。
  • sas-at 定义连接的时间。
  • sas-expiry 定义提供的 SAS 的过期时间。
  • client-agent 可以选择性地传达与创建连接的客户端相关的信息。

注释

在整个规范中,Authentication Method 和具有大写名称的其他属性都是 MQTT 5 中的一类属性 - MQTT 5 规范中对其进行了详细介绍。 api-version 和短划线形式的其他属性是特定于 IoT 中心 API 的用户属性。

IoT 中心在完成身份验证并获取为连接提供支持所需的数据后,会使用 CONNACK 数据包进行响应。 如果成功地建立了连接,则 CONNACK 将如下所示:

<- CONNACK
    Session_Present: 1
    Reason_Code: 0x00
    Session_Expiry_Interval: 0xFFFFFFFF # included only if CONNECT specified value less than 0xFFFFFFFF and more than 0x00
    Receive_Maximum: 16
    Maximum_QoS: 1
    Retain_Available: 0
    Maximum_Packet_Size: 262144
    Topic_Alias_Maximum: 10
    Subscription_Identifiers_Available: 0
    Shared_Subscriptions_Available: 0
    Server_Keep_Alive: 1140 # included only if client did not specify Keep Alive or if it specified a bigger value

这些 CONNACK 数据包属性遵循 MQTT 5 规范。 它们反映 IoT 中心的功能。

身份验证

Authentication Method 客户端上的 CONNECT 属性定义它用于此连接的身份验证类型:

  • SAS - 共享访问签名在 CONNECTAuthentication Data 属性中提供。
  • X509 - 客户端依赖于客户端证书身份验证。

如果身份验证方法与客户端在 IoT 中心的已配置方法不匹配,则身份验证会失败。

注释

此 API 要求在 Authentication Method 数据包中设置 CONNECT 属性。 如果未提供 Authentication Method 属性,则连接会失败,并出现“Bad Request”响应。

不支持以前的 API 版本中使用的用户名/密码身份验证。

SAS

对于基于 SAS 的身份验证,客户端必须提供连接上下文的签名。 该签名可证明 MQTT 连接的真实性。 该签名必须基于 IoT 中心客户端配置中两个身份验证密钥之一。 或者,它必须基于共享访问策略的两个共享访问密钥之一。

要签名的字符串必须采用如下格式:

{host name}\n
{Client Id}\n
{sas-policy}\n
{sas-at}\n
{sas-expiry}\n
  • host name 派生自 SNI 扩展(在 TLS 握手期间由客户端在客户端 Hello 记录中提供)或 host 数据包中的 CONNECT 用户属性。
  • Client IdCONNECT 数据包中的客户端标识符。
  • sas-policy - 如果存在,则此项会定义用于身份验证的 IoT 中心访问策略。 它被编码为 CONNECT 数据包上的用户属性。 可选:省略它意味着会改用设备注册表中的身份验证设置。
  • sas-at - 如果存在,则此项会指定连接时间 - 当前时间。 它被编码为 time 类型的用户属性,在 CONNECT 数据包上。
  • sas-expiry 定义身份验证的过期时间。 它是 time 数据包上 CONNECT 类型的用户属性。 此属性是必需的。

对于可选参数,如果省略,则必须在要签名的字符串中改用空字符串。

HMAC-SHA256 用来基于设备的对称密钥对之一对字符串进行哈希处理。 然后,哈希值将设置为 Authentication Data 属性的值。

X509

如果 Authentication Method 属性设置为 X509,则 IoT 中心会根据提供的客户端证书对连接进行身份验证。

重新身份验证

如果使用基于 SAS 的身份验证,建议使用生存期短的身份验证令牌。 为了让连接保持经身份验证的状态并防止因过期而断开连接,客户端必须通过发送带有 AUTH(重新身份验证)的 Reason Code: 0x19 数据包来重新进行身份验证:

-> AUTH
    Reason_Code: 0x19
    Authentication_Method: SAS
    Authentication_Data: {SAS bytes}
    sas-at: {current time}
    sas-expiry: {SAS expiry time}

规则:

  • Authentication Method 必须与用于初始身份验证的身份验证方法相同
  • 如果最初使用基于共享访问策略的 SAS 对连接进行身份验证,则在重新身份验证中使用的签名必须基于同一策略。

如果重新身份验证成功,IoT 中心会发送包含 AUTH(成功)的 Reason Code: 0x00 数据包。 否则,IoT 中心会发送包含 DISCONNECT(未授权)的 Reason Code: 0x87 数据包并关闭连接。

断开连接

服务器可能会断开与客户端的连接,原因如下:

  • 客户端行为方式异常,无法直接以否定确认(或响应)的形式作出回应。
  • 服务器无法让连接状态保持最新,
  • 另一个客户端使用相同标识进行连接。

服务器可能因 MQTT 5.0 规范中定义的任何原因代码而断开连接。 值得注意的提及事项:

  • 135(未授权),在重新身份验证失败、当前 SAS 令牌过期或设备的凭据更改时出现。
  • 142(会话已被占用),当打开具有相同客户端标识的新连接时出现。
  • 159(超出连接速率),在 IoT 中心的连接速率超出限制时出现。
  • 131(特定于实现的错误),用于此 API 中定义的任何自定义错误。 statusreason 属性用于传达有关连接断开原因的更多详细信息(有关详细信息,请参阅响应)。

运营

此 API 中的所有功能都表示为操作。 下面是“发送遥测数据”操作的示例:

-> PUBLISH
    QoS: 1
    Packet_Id: 3
    Topic: $iothub/telemetry
    Payload: Hello

<- PUBACK
    Packet_Id: 3
    Reason_Code: 0

有关此 API 中操作的完整规范,请参阅 IoT 中心数据平面 MQTT 5 API 参考

注释

此规范中的所有示例都是从客户端的角度展示的。 -> 符号表示发送数据包的客户端,<- 表示接收数据包的客户端。

消息主题和订阅

在此 API 的操作消息中使用的主题以 $iothub/ 开头。 MQTT 中转站语义不适用于这些操作(有关详细信息,请参阅“以 $ 开头的主题”)。 不支持以 $iothub/ 开头且未在此 API 中定义的主题:

  • 向未定义的主题发送消息会导致“Not Found”响应(有关详细信息,请参阅响应),
  • 订阅未定义的主题会导致 SUBACKReason Code: 0x8F(主题筛选器无效)。

主题名称和属性名称区分大小写,必须完全匹配。 例如,$iothub/telemetry/ 不受支持,而 $iothub/telemetry 受支持。

注释

$iothub/.. 下的订阅中不支持使用通配符。 也就是说,客户端无法订阅 $iothub/+$iothub/#。 尝试这样做会导致 SUBACK 和“Reason Code: 0xA2”的出现(不支持通配符订阅)。 在包含路径参数的操作的主题名称中,仅支持使用单段通配符 (+) 来代替路径参数。

交互类型

此 API 中的所有操作都基于以下两种交互类型之一:

  • 带有可选确认的消息 (MessageAck)
  • 请求-响应 (ReqRep)

操作也因方向而异(取决于交换的初始消息的方向):

  • 客户端到服务器 (C2S)
  • 服务器到客户端 (s2c)

例如,“发送遥测数据”是“具有确认的消息”类型的客户端到服务器操作,而“处理直接方法”是“请求-响应”类型的服务器到客户端操作。

消息-确认交互

在 MQTT 中,“带有可选确认的消息” (MessageAck) 交互表示为 PUBLISHPUBACK 数据包的交换。 确认是可选操作,发送方可以通过发送带 PUBLISHQoS: 0 数据包来选择不请求该确认。

注释

如果因客户端声明的 PUBACK 而必须截短 Maximum Packet Size 数据包中的属性,IoT 中心将尽可能保留在给定限制内的用户属性。 首先列出的用户属性比后面列出的有更高的机会发送;Reason String 属性的优先级最低。

MessageAck 简单交互的示例

消息:

PUBLISH
    QoS: 1
    Packet_Id: 34
    Topic: $iothub/{request.path}
    Payload: <any>

确认(成功):

PUBACK
    Packet_Id: 34
    Reason_Code: 0

请求-响应交互

在请求-响应 (ReqRep) 交互中,请求和响应都转换为带 PUBLISHQoS: 0 数据包。

Correlation Data 属性必须在二者中都设置,用于将响应数据包与请求数据包匹配。

此 API 对所有 ReqRep 操作使用单个响应主题,即 $iothub/responses。 对于客户端到服务器的操作,不需要订阅/取消订阅本主题 - 服务器假定所有客户端都已进行了订阅。

简单的请求应答交互示例

请求:

PUBLISH
    QoS: 0
    Topic: $iothub/{request.path}
    Correlation_Data: 0x01 0xFA
    Payload: ...

响应(成功):

PUBLISH
    QoS: 0
    Topic: $iothub/responses
    Correlation_Data: 0x01 0xFA
    Payload: ...

ReqRep 交互不支持将带 PUBLISHQoS: 1 数据包作为请求或响应消息。 发送请求 PUBLISH 会导致“Bad Request”响应。

Correlation Data 属性中支持的最大长度为 16 字节。 如果 Correlation Data 数据包上的 PUBLISH 属性设置为长度超过 16 字节的值,则 IoT 中心会发送结果为“DISCONNECT”的 Bad Request,并关闭连接。 此行为仅适用于在此 API 中交换的数据包。

注释

关联数据是任意字节的序列,例如,它不一定是 UTF-8 字符串。

ReqRep 使用预定义的主题进行响应;请求 PUBLISH 数据包中的“响应主题”属性(如果已由发送方设置)会被忽略。

IoT 中心会自动为客户端订阅所有客户端到服务器 ReqRep 操作的响应主题。 即使客户端显式取消了响应主题的订阅,IoT 中心也会自动恢复订阅。 对于服务器到客户端的 ReqRep 交互,设备仍需进行订阅。

消息属性

操作属性(系统定义的或用户定义的)以 MQTT 5 中的数据包属性的形式表示。

用户属性名称具有大小写敏感性,必须与定义中的拼写完全一致。 例如,Trace-ID 不受支持,而 trace-id 受支持。

如果请求的用户属性超出规范,且没有前缀 @,则会导致错误。

系统属性编码为一类属性(例如 Content Type)或用户属性。 规范提供了支持的系统属性的详尽列表。 所有一类属性都会被忽略,除非规范中显式声明了对它们的支持。

如果允许使用用户定义的属性,则其名称必须遵循 @{property name} 格式。 用户定义的属性仅支持有效的 UTF-8 字符串值。 例如,值为 MyProperty115 属性必须编码为名称为 @MyProperty 且值为 15 的用户属性。

如果 IoT 中心无法识别用户属性,系统会将其视为错误,IoT 中心会使用包含 PUBACK(特定于实现的错误)和 Reason Code: 0x83(错误的请求)的 status: 0100 进行响应。 如果未请求确认 (QoS: 0),则会发回具有相同错误的 DISCONNECT 数据包,并终止连接。

string 外,此 API 还定义下列数据类型:

  • time:自 1970-01-01T00:00:00.000Z 以来的毫秒数。 例如,1600987195320 表示 2020-09-24T22:39:55.320Z
  • u32:无符号的 32 位整数。
  • u64:无符号的 64 位整数。
  • i32:带符号的 32 位整数。

响应

交互可能会产生不同的结果:SuccessBad RequestNot Found,等等。 可通过 status 用户属性将结果彼此区分开来。 Reason Code 数据包中的 PUBACK(适用于 MessageAck 交互)在涵义上与 status 匹配(如果可能)。

注释

如果客户端在 CONNECT 数据包中指定“Request Problem Information: 0”,则不会按照 MQTT 5 规范发送 PUBACK 数据包上的任何用户属性,包括 status 属性。 在这种情况下,客户端仍然可以根据 Reason Code 来确定确认是肯定的还是否定的。

每个交互都有一个默认选项(或成功)。 它具有Reason Code0,以及status属性为“未设置”。 否则:

  • 对于 MessageAck 交互,PUBACK 会获得 Reason Code(除 0x0(成功)以外)。 可以提供 status 属性来进一步阐明结果。
  • 对于 ReqRep 交互,响应 PUBLISH 会获得 status 属性集。
  • 由于无法直接使用 QoS: 0 来响应 MessageAck 交互,因此将改为发送包含响应信息的 DISCONNECT 数据包,然后断开连接。

例子:

错误的请求 (MessageAck):

PUBACK
    Reason_Code: 131
    status: 0100
    reason: Unknown property `test`

未授权(MessageAck):

PUBACK
    Reason_Code: 135
    status: 0101

未授权 (ReqRep):

PUBLISH
    QoS: 0
    Topic: $iothub/responses
    Correlation_Data: ...
    status: 0101

在需要时,IoT 中心会设置以下用户属性:

  • status - IoT 中心的扩展代码,表示操作的状态。 此代码可用于区分结果。
  • trace-id - 用于操作的追踪 ID;IoT Hub 可能会保留关于操作的更多诊断信息,这些信息可用于内部调查。
  • reason - 可供用户阅读的消息,其提供的详细信息说明了为何操作最终处于 status 属性所指示的状态。

注释

如果客户端将 CONNECT 数据包中的 Maximum Packet Size 属性设置为一个很小的值,则不是所有用户属性都合适并会出现在数据包中。

reason 仅适用于人员,不应在客户端逻辑中使用。 此 API 允许在任何时间点更改消息(没有警告),而无需更改版本。

如果客户端在 CONNECT 数据包中发送 RequestProblemInformation: 0,则用户属性不会按 MQTT 5 规范包含在确认中。

状态代码

status 属性带有操作的状态代码。 它经过优化,可以提高计算机读取效率。 它包含在字符串中编码为十六进制的双字节无符号整数,例如 0501。 代码结构(位图):

7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0
0 0 0 0 0 R T T | C C C C C C C C

第一个字节用于标志:

  • 0 位和 1 位表示结果类型:
    • 00 - 成功
    • 01 - 客户端错误
    • 10 - 服务器错误
  • 2 位:1 表示错误可重试
  • 3 到 7 位为保留位,必须设置为 0

第二个字节包含不同的实际响应代码。 不同标志的错误代码可能具有相同的第二个字节值。 例如,可能会出现 0001010102010301 错误代码,其涵义各不相同。

例如,Too Many Requests 是客户端的可重试错误,其自身的代码为 1。 其值为 0000 0101 0000 00010x0501

客户端可以使用类型位来确定操作是否已成功结束。 客户端还可以使用可重试位来确定是否应重试操作。

建议

会话管理

CONNACK 数据包带有 Session Present 属性,用于指示服务器是否还原了以前创建的会话。 使用此属性来确定是订阅主题还是跳过订阅操作(因为之前已订阅过)。

若要依赖 Session Present,客户端必须跟踪所做的订阅(即,发送了 SUBSCRIBE 数据包,并收到含有成功原因代码的 SUBACK),或者确保在一次 SUBSCRIBE/SUBACK 交换中订阅所有主题。 否则,如果客户端发送两个 SUBSCRIBE 数据包,而服务器仅成功处理其中的一个,则服务器会在 Session Present: 1 中传达 CONNACK,同时仅接受客户端的订阅的一部分。

若要防止版本较早的客户端未订阅所有主题这一情况,最好是在客户端行为发生更改(例如,在固件更新过程中发生更改)时无条件地进行订阅。 另外,为了确保不会保留占用最大允许订阅数的过时订阅,请显式取消那些不再使用的订阅。

批处理

不需通过特殊格式来发送一批消息。 为了减少 TLS 和网络中资源密集型操作的开销,请在将数据包(PUBLISHPUBACKSUBSCRIBE,等等)提交到底层的 TLS/TCP 堆栈之前将它们捆绑在一起。 另外,客户端还可以在“批”内更轻松地创建主题别名。

  • 将完整的主题名称置于连接的第一个 PUBLISH 数据包中,然后将主题别名与其关联。
  • 为同一主题放置以下数据包,其中包含空的主题名称和主题别名属性。

迁移

本部分列出了 API 的更改与以前的 MQTT 支持的比较。

  • 传输协议为 MQTT 5。 以前的为 MQTT 3.1.1。
  • SAS 身份验证的上下文信息直接包含在 CONNECT 数据包中,而不是与签名一起进行编码。
  • “身份验证方法”用于指示所使用的身份验证方法。
  • “共享访问签名”放置在“身份验证数据”属性中。 过去使用“密码”字段。
  • 操作主题不同:
    • 遥测:$iothub/telemetry 代替 devices/{Client Id}/messages/events
    • 命令:$iothub/commands 代替 devices/{Client Id}/messages/devicebound
    • 补丁孪生体已被报告:$iothub/twin/patch/reported 替代 $iothub/twin/PATCH/properties/reported
    • 通知数字孪生期望状态已更改:$iothub/twin/patch/desired 而不是 $iothub/twin/PATCH/properties/desired
  • 针对客户端-服务器请求-响应操作的响应主题的订阅不是必需的。
  • 使用用户属性,而不是对主题名称段中的属性进行编码。
  • 属性名称的拼写采用“短横线命名法”的命名约定,而不是使用带有特殊前缀的缩写。 用户定义的属性现在改为需要前缀。 例如,$.mid 现在为 message-id,而 myProperty1 则变为 @myProperty1
  • “关联数据”属性用于关联请求-响应操作的请求消息和响应消息,代替在主题中编码的 $rid 属性。
  • iothub-connection-auth-method 属性不再标记在遥测事件上。
  • 在缺少订阅的情况下,不会从设备中清除 C2D 命令。 它们会保持排队状态,直到设备进行订阅,或者它们过期。

例子

发送遥测数据

消息:

-> PUBLISH
    QoS: 1
    Packet_Id: 31
    Topic: $iothub/telemetry
    @myProperty1: My String Value # optional
    creation-time: 1600987195320 # optional
    @ No_Rules-ForUser-PROPERTIES: Any UTF-8 string value # optional
    Payload: <data>

致谢:

<- PUBACK
    Packet_Id: 31
    Reason_Code: 0

替代确认(限流):

<- PUBACK
    Packet_Id: 31
    Reason_Code: 151
    status: 0501

发送获取孪生体的状态

请求:

-> PUBLISH
    QoS: 0
    Topic: $iothub/twin/get
    Correlation_Data: 0x01 0xFA
    Payload: <empty>

响应(成功):

<- PUBLISH
    QoS: 0
    Topic: $iothub/responses
    Correlation_Data: 0x01 0xFA
    Payload: <twin/desired state>

响应(不允许):

<- PUBLISH
    QoS: 0
    Topic: $iothub/responses
    Correlation_Data: 0x01 0xFA
    status: 0102
    reason: Operation not allowed for `B2` SKU
    Payload: <empty>

处理直接方法调用

请求:

<- PUBLISH
    QoS: 0
    Topic: $iothub/methods/abc
    Correlation_Data: 0x0A 0x10
    Payload: <data>

响应(成功):

-> PUBLISH
    QoS: 0
    Topic: $iothub/responses
    Correlation_Data: 0x0A 0x10
    response-code: 200 # user defined response code
    Payload: <data>

注释

status 未设置——表示成功响应。

“设备不可用”响应:

-> PUBLISH
    QoS: 0
    Topic: $iothub/responses
    Correlation_Data: 0x0A 0x10
    status: 0603

使用 QoS 0 时出错,第 1 部分

请求:

-> PUBLISH
    QoS: 0
    Topic: $iothub/twin/gett # misspelled topic name - server won't recognize it as Request-Response interaction
    Correlation_Data: 0x0A 0x10
    Payload: <data>

响应:

<- DISCONNECT
    Reason_Code: 144
    reason: "Unsupported topic: `$iothub/twin/gett`"

使用 QoS 0 时出错,第 2 部分

请求:

-> PUBLISH # missing Correlation Data
    QoS: 0
    Topic: $iothub/twin/get
    Payload: <data>

响应:

<- DISCONNECT
    Reason_Code: 131
    status: 0100
    reason: "`Correlation Data` property is missing"

后续步骤