8.3 消息模块
消息模块描述了 RTPS 写入器端点和 RTPS 读取器端点之间交换的消息的整体结构和逻辑内容。RTPS 消息设计上是模块化的,可以轻松扩展以支持标准协议功能的添加以及供应商特定的扩展。
8.3.1 概述
消息模块的组织如下:
-
8.3.2 介绍了定义 RTPS 消息所需的任何额外类型。
-
8.3.3 描述了所有 RTPS 消息通用的结构。所有 RTPS 消息都由一个头部和一系列子消息组成。单个 RTPS 消息中可以发送的子消息数量仅受底层传输支持的最大消息大小的限制。
-
某些子消息可能会影响对同一 RTPS 消息中后续子消息的解释。解释子消息的上下文由 RTPS 消息接收器维护,并在 8.3.4 中描述。
-
8.3.5 列出了创建子消息的基本构建模块,也称为 SubmessageElements。这包括序列号集、时间戳、标识符等。
-
8.3.6 描述了 RTPS 头部的结构。固定大小的 RTPS 头部用于标识一个 RTPS 消息。
-
最后,8.3.7 详细介绍了所有可用的子消息。对于每个子消息,规范定义了其内容、何时被认为有效以及它如何影响 RTPS 消息接收器的状态。PSM 将在 9.4.5 中定义这些子消息到线上实际映射的每个位和字节。
8.3.2 类型定义
除了 8.2.1.2 中定义的类型外,消息模块还使用了表 8.13 中列出的类型。
表 8.13 - 用于定义 RTPS 消息的类型
类型 | 用途 |
ProtocolId_t | 用于识别协议的枚举。以下值由协议保留:PROTOCOL_RTPS |
SubmessageFlag | 用于指定子消息标志的类型。子消息标志取布尔值,并影响接收方解析子消息。 |
SubmessageKind | 用于识别子消息类型的枚举。以下值由此版本的协议保留:DATA, GAP, HEARTBEAT, ACKNACK, PAD, INFO_TS, INFO_REPLY, INFO_DST, INFO_SRC, DATA_FRAG, NACK_FRAG, HEARTBEAT_FRAG |
Time_t | 用于保存时间戳的类型。应至少具有纳秒级分辨率。以下值由协议保留:TIME_ZERO, TIME_INVALID, TIME_INFINITE |
Count_t | 用于保存单调递增的计数,用于识别消息重复。 |
ParameterId_t | 用于在参数列表中唯一标识参数的类型。主要由发现模块广泛使用,主要用于定义 QoS 参数。一个值范围保留给协议定义的参数,另一个范围可用于供应商定义的参数,见 8.3.5.9。 |
FragmentNumber_t | 用于保存片段编号的类型。必须能够使用 32 位表示。 |
GroupDigest_t | 用于保存唯一标识同一参与者所属实体组的摘要值的类型。 |
8.3.3 RTPS 消息的整体结构
RTPS 消息的整体结构由一个固定大小的 RTPS 头部和可变数量的 RTPS 子消息部分组成。每个子消息又由一个 SubmessageHeader 和可变数量的 SubmessageElements 组成。这在图 8.8 中进行了说明。
图 8.8 - RTPS 消息的结构
RTPS 协议发送的每条消息都有一个有限的长度。这个长度不是由 RTPS 协议显式发送的,而是 RTPS 消息发送时所用底层传输的一部分。在面向包的传输(如 UDP/IP)的情况下,消息的长度已由传输头部提供。面向流的传输(如 TCP)需要在消息前插入长度,以确定 RTPS 消息的边界。
8.3.3.1 头部结构
RTPS 头部必须出现在每条消息的开始处。
图 8.9 - RTPS 消息头部的结构
头部标识消息属于 RTPS 协议。头部标识了协议的版本和发送消息的供应商。头部包含表 8.14 中列出的字段。
表 8.14 - 头部的结构
字段 | 类型 | 含义 |
protocol | ProtocolId_t | 标识消息为 RTPS 消息。 |
version | ProtocolVersion_t | 标识 RTPS 协议的版本。 |
vendorId | VendorId_t | 表示提供 RTPS 协议实现的供应商。 |
guidPrefix | GuidPrefix_t | 定义用于消息中出现的所有 GUID 的默认前缀。 |
RTPS 头部的结构在协议的这个主要版本(2)中不能更改。
8.3.3.1.1 protocol
协议将消息标识为 RTPS 消息。这个值被设置为 PROTOCOL_RTPS。
8.3.3.1.2 version
版本标识了 RTPS 协议的版本。遵循本文档版本的实现实现了协议版本 2.4(主版本 = 2,次版本 = 4),并将此字段设置为 PROTOCOLVERSION。
8.3.3.1.3 vendorId
vendorId 标识了实现 RTPS 协议的中间件的供应商,并允许此供应商向协议添加特定的扩展。vendorId 不指的是包含 RTPS 中间件的设备或产品的供应商。vendorId 的可能值由 OMG 分配。 协议保留以下值: VENDORID_UNKNOWN 只有承诺遵守当前协议主版本的实现者才能保留供应商 ID。为了促进逐步发展,供应商 ID 列表与本规范分开管理。该列表维护在 OMG DDS 网站上,可在以下地址访问:新的供应商 ID 请求应通过电子邮件发送至
8.3.3.1.4 guidPrefix
guidPrefix 定义了一个默认前缀,可用于重构消息中包含的子消息中出现的全局唯一标识符(GUID)。guidPrefix 允许子消息仅包含 GUID 的 EntityId 部分,因此可以避免在每个 GUID 上重复通用前缀(见 8.2.4.1)。
8.3.3.2 子消息结构
每个 RTPS 消息由可变数量的 RTPS 子消息部分组成。所有 RTPS 子消息都具有相同的结构,如图 8.10 所示。
图8.10 RTPS子消息的结构
所有子消息都以一个 SubmessageHeader 部分开始,后跟 SubmessageElement 部分的串联。SubmessageHeader 标识子消息的种类和该子消息中的可选元素。SubmessageHeader 包含表 8.15 中列出的字段。
表 8.15 - SubmessageHeader 的结构
字段 | 类型 | 含义 |
submessageId | SubmessageKind | 标识子消息的种类。可能的子消息在 8.3.7 中描述。 |
flags | SubmessageFlag[8] | 标识用于编码子消息的字节顺序,子消息中可选元素的存在,并可能修改对子消息的解释。有 8 个可能的标志。第一个标志(索引 0)标识用于编码子消息的字节顺序。其余标志根据子消息的种类不同而有不同的解释,并且对于每种子消息分别描述。 |
submessageLength | ushort | 表示子消息的长度。鉴于 RTPS 消息由子消息的串联组成,子消息长度可以用来跳转到下一个子消息。 |
RTPS 子消息的结构在协议的这个主要版本(2)中不能更改。
8.3.3.2.1 SubmessageId
submessageId 标识子消息的种类。有效的 ID 由 SubmessageKind 的可能值枚举(见表 8.13)。在这个主要版本(2)中,子消息 ID 的含义不能修改。在更高的次版本中可以添加额外的子消息。为了保持与未来版本的互操作性,平台特定映射应该保留一系列值用于协议扩展,以及一系列值专门用于供应商特定的子消息,这些值将来不会被 RTPS 协议的未来版本使用。
8.3.3.2.2 flags
子消息头部的标志包含 8 个布尔值。第一个标志,EndiannessFlag,在所有子消息中都存在并位于相同位置,代表用于编码子消息中信息的字节顺序。字面值 ‘E’ 常用于指代 EndiannessFlag。如果 EndiannessFlag 设置为 FALSE,则子消息以大端格式编码,EndiannessFlag 设置为 TRUE 表示小端。其他标志的解释取决于子消息的类型。
8.3.3.2.3 submessageLength
指示子消息的长度(不包括子消息头部)。如果 submessageLength > 0,它要么是:
-
从子消息内容的开始到下一个子消息头部开始的长度(如果子消息不是消息中的最后一个子消息)。
-
或者是剩余的消息长度(如果子消息是消息中的最后一个子消息)。消息的解释者可以区分这两种情况,因为它知道消息的总长度。
如果 submessageLength==0,子消息是消息中的最后一个子消息,并延伸到消息的末尾。这使得可以发送大于 64k 的子消息(这是 submessageLength 字段可以存储的最大长度),只要它们是消息中的最后一个子消息。
8.3.4 RTPS 消息接收器
消息中的子消息的解释和含义可能取决于同一消息中包含的先前子消息。因此,消息的接收者必须保持来自同一消息中先前反序列化子消息的状态。这种状态被建模为每次处理新消息时重置的 RTPS 接收器的状态,并为每个子消息的解释提供上下文。RTPS 接收器在图 8.11 中展示。表 8.16 列出了用于表示 RTPS 接收器状态的属性。
图8.11 RTPS 接收器
对于每个新消息,接收器的状态都会重置并初始化,如下所列。
表 8.16 - 接收器的初始状态
名称 | 初始值 |
sourceVersion | PROTOCOLVERSION |
sourceVendorId | VENDORID_UNKNOWN |
sourceGuidPrefix | GUIDPREFIX_UNKNOWN |
destGuidPrefix | 参与者接收消息的 GUID 前缀 |
UnicastReplyLocatorList | 初始化为包含单个 Locator_t,具有以下字段:<br>• LocatorKind:标识接收消息的传输种类(例如,LOCATOR_KIND_UDPv4)<br>• Address:消息来源的地址,如果传输支持(例如 UDP),否则为 LOCATOR_ADDRESS_INVALID<br>• 端口:LOCATOR_PORT_INVALID |
multicastReplyLocatorList | 初始化为包含单个 Locator_t,具有以下字段:<br>• LocatorKind:标识接收消息的传输种类(例如,LOCATOR_KIND_UDPv4)<br>• 地址:LOCATOR_ADDRESS_INVALID<br>• 端口:LOCATOR_PORT_INVALID |
haveTimestamp | FALSE |
timestamp | TIME_INVALID |
messageLength |
8.3.4.1 消息接收器遵循的规则
以下算法概述了任何消息的接收者必须遵循的规则:
-
如果无法读取完整的子消息头,则认为消息的其余部分无效。
-
submessageLength 字段定义下一个子消息的开始位置,或表明子消息延伸到消息的末尾,如第 8.3.3.2.3 节所述。如果此字段无效,则消息的其余部分无效。
-
具有未知 SubmessageId 的子消息必须被忽略,并继续解析下一个子消息。具体来说:RTPS 2.4 的实现必须忽略版本 2.4 中 SubmessageKind 集合之外的任何子消息 ID。来自未知 vendorId 的供应商特定范围内的 SubmessageIds 也必须被忽略,解析必须继续进行到下一个子消息。
-
子消息标志。子消息的接收者应忽略未知标志。RTPS 2.4 的实现应跳过协议中标记为 “X”(未使用)的所有标志。
-
有效的 submessageLength 字段必须始终用于查找下一个子消息,即使对于已知 ID 的子消息也是如此。
-
已知但无效的子消息会使消息的其余部分无效。第 8.3.7 小节描述了每个已知子消息及其被认为无效的情况。有效的头部和/或子消息的接收有两个效果:
-
它可以改变接收器的状态;这种状态影响如何解释消息中的后续子消息。第 8.3.7 节讨论了每个子消息的状态变化。在此协议版本中,只有头部和 InfoSource、InfoReply、InfoDestination 和 InfoTimestamp 子消息会改变接收器的状态。
-
它可以影响消息所针对的端点的行为。这适用于基本的 RTPS 消息:Data, DataFrag, HeartBeat, AckNack, Gap, HeartbeatFrag, NackFrag。第 8.3.7 小节描述了头部和每个子消息的详细解释。
-
8.3.5 RTPS 子消息元素
每个 RTPS 消息包含可变数量的 RTPS 子消息。每个 RTPS 子消息反过来是由一组预定义的原子构建块组成,称为 SubmessageElements。RTPS 2.4 定义了以下子消息元素:GuidPrefix, EntityId, SequenceNumber, SequenceNumberSet, FragmentNumber, FragmentNumberSet, VendorId, ProtocolVersion, LocatorList, Timestamp, Count, SerializedData, ParameterList 和 GroupDigest。
图8.12 RTPS 子消息元素
8.3.5.1 GuidPrefix 和 EntityId
这些子消息元素用于在子消息中包含 GUID_t(在 8.2.4.1 中定义)的 GuidPrefix_t 和 EntityId_t 部分。
表 8.17 - GuidPrefix 子消息元素的结构
字段 | 类型 | 含义 |
value | GuidPrefix_t | 标识消息的源或目标实体的 GUID_t 中的 GuidPrefix_t 部分。 |
表 8.18 – EntityId 子消息元素的结构
字段 | 类型 | 含义 |
value | EntityId_t | 标识消息的源或目标实体的 GUID_t 中的 EntityId_t 部分。 |
8.3.5.2 VendorId
VendorId 标识实现 RTPS 协议的中间件的供应商,并允许该供应商向协议添加特定的扩展。vendor ID 不指的是包含 DDS 中间件的设备或产品的供应商。
表 8.19 – VendorId 子消息元素的结构
字段 | 类型 | 含义 |
value | VendorId_t | 标识实现协议的中间件的供应商。 |
协议保留以下值:
VENDORID_UNKNOWN
其他值必须由 OMG 分配。
8.3.5.3 ProtocolVersion
ProtocolVersion 定义了 RTPS 协议的版本。
表 8.20 - ProtocolVersion 子消息元素的结构
字段 | 类型 | 含义 |
value | ProtocolVersion_t | 标识 RTPS 协议的主要和次要版本。 |
RTPS 协议版本 2.4 定义了以下特殊值:
PROTOCOLVERSION_1_0
PROTOCOLVERSION_1_1
PROTOCOLVERSION_2_0
PROTOCOLVERSION_2_1
PROTOCOLVERSION_2_2
PROTOCOLVERSION_2_2
PROTOCOLVERSION_2_4
8.3.5.4 SequenceNumber
序列号是一个 64 位有符号整数,其取值范围为:-2^63 <= N <= 2^63-1。选择 64 位作为序列号的表示确保序列号永不环绕。序列号从 1 开始。
表 8.21 - SequenceNumber 子消息元素的结构
字段 | 类型 | 含义 |
value | SequenceNumber_t | 提供 64 位序列号的值。<br>协议保留以下值:<br>SEQUENCENUMBER_UNKNOWN |
8.3.5.5 SequenceNumberSet
SequenceNumberSet 子消息元素被用作多个消息的一部分,提供关于范围内单个序列号的二进制信息。SequenceNumberSet 中表示的序列号被限制为属于不大于 256 的区间。换句话说,有效的 SequenceNumberSet 必须验证:
maximum(SequenceNumberSet) - minimum(SequenceNumberSet) < 256
minimum(SequenceNumberSet) >= 1
上述限制允许 SequenceNumberSet 使用位图以高效紧凑的方式表示。
SequenceNumberSet 子消息元素被用来例如有选择地请求重新发送一组序列号。
表 8.22 - SequenceNumberSet 子消息元素的结构
字段 | 类型 | 含义 |
base | SequenceNumber_t | 标识集合中的第一个序列号。 |
set | SequenceNumber_t[*] | 一组序列号,每个都满足条件:base <= element(set) <= base+255 |
8.3.5.6 FragmentNumber
片段号是一个 32 位无符号整数,用于子消息标识分段序列化数据中的特定片段。片段号从 1 开始。
表 8.23 - FragmentNumber 子消息元素的结构
字段 | 类型 | 含义 |
value | FragmentNumber_t | 提供 32 位片段号的值。 |
8.3.5.7 FragmentNumberSet
FragmentNumberSet 子消息元素用于提供关于范围内单个片段号的二进制信息。FragmentNumberSet 中表示的片段号被限制为属于不大于 256 的区间。换句话说,有效的 FragmentNumberSet 必须验证:
maximum(FragmentNumberSet) - minimum(FragmentNumberSet) < 256
minimum(FragmentNumberSet) >= 1
上述限制允许使用位图以高效紧凑的方式表示 FragmentNumberSet。
FragmentNumberSet 子消息元素用于例如有选择地请求重新发送一组片段。
表 8.24 - FragmentNumberSet 子消息元素的结构
字段 | 类型 | 含义 |
base | FragmentNumber_t | 标识集合中的第一个片段号。 |
set | FragmentNumber_t[*] | 一组片段号,每个都满足条件:base <= element(set) <= base+255 |
8.3.5.8 Timestamp
Timestamp 用于表示时间。表示应能够具有纳秒或更好的分辨率。
表 8.25 - Timestamp 子消息元素的结构
字段 | 类型 | 含义 |
value | Time_t | 提供时间戳的值。 |
协议使用以下三个特殊值:
TIME_ZERO
TIME_INVALID
TIME_INFINITE
8.3.5.9 ParameterList
ParameterList 用作多个消息的一部分,包含可能影响消息解释的 QoS 参数。参数的表示遵循一种机制,允许对 QoS 进行扩展而不破坏向后兼容性。
表 8.26 - ParameterList 子消息元素的结构
字段 | 类型 | 含义 |
parameter | Parameter[*] | 参数列表 |
表 8.27 - ParameterList 子消息元素中每个参数的结构
字段 | 类型 | 含义 |
parameterId | ParameterId_t | 唯一标识一个参数 |
length | short | 参数值的长度 |
value | octet[length] | 参数值 |
ParameterList 的实际表示由每个 PSM 定义。然而,为了支持 PSM 之间的互操作性或桥接,并允许保留向后兼容性的扩展,所有 PSM 使用的表示必须遵守以下规则:
-
ParameterId_t parameterId 的可能值不得超过 2^16。
-
2^15 的值范围保留给协议定义的参数。协议 2.4 版本和同一主版本的所有未来修订都必须使用此范围内的值。
-
2^15 的值范围保留给供应商定义的参数。协议 2.4 版本及其对应于同一主版本的任何未来修订都不允许使用此范围内的值。
-
任何参数的最大长度限制为 2^16 字节。
在上述约束条件下,不同的 PSM 可能选择不同的 ParameterId_t 表示。例如,一个 PSM 可能使用短整数表示 parameterId,而另一个 PSM 可能使用字符串。
8.3.5.10 Count
Count 由几个子消息使用,使接收器能够检测到相同子消息的重复。
表 8.28 - Count 子消息元素的结构
字段 | 类型 | 含义 |
value | Count_t | 计数值 |
8.3.5.11 LocatorList
LocatorList 用于指定一系列定位器。
表 8.29 - LocatorList 子消息元素的结构
字段 | 类型 | 含义 |
value | Locator_t[*] | 定位器列表 |
8.3.5.12 SerializedData
SerializedData 包含数据对象值的序列化表示。RTPS 协议不解释序列化的数据流。因此,它被表示为不透明数据。有关额外信息,请参见第 10 节序列化负载表示。
表 8.30 - SerializedData 子消息元素的结构
字段 | 类型 | 含义 |
value | octet[*] | 序列化数据流 |
8.3.5.13 SerializedDataFragment
SerializedDataFragment 包含已被分片的数据对象的序列化表示。与未分片的 SerializedData 一样,RTPS 协议不解释分片的序列化数据流。因此,它被表示为不透明数据。有关额外信息,请参见序列化载荷表示。
表 8.31 - SerializedDataFragment
字段 | 类型 | 含义 |
value | octet[*] | 序列化数据流片段 |
8.3.5.14 GroupDigest
GroupDigest 用于以紧凑方式通信一组 EntityId_t。
表 8.32 - GroupDigest 子消息元素的结构
字段 | 类型 | 含义 |
value | GroupDigest_t | 用于保存唯一标识同一参与者所属实体组的摘要值的类型。 |
8.3.6 RTPS 头部
如 8.3.3 所述,每个 RTPS 消息必须以头部开始。
8.3.6.1 目的
头部用于将消息标识为属于 RTPS 协议,标识所使用的 RTPS 协议版本,并提供适用于消息中包含的子消息的上下文信息。
8.3.6.2 内容
构成头部结构的元素在 8.3.3.1 中描述。只有在协议的主要版本也发生变化时,头部的结构才能更改。
8.3.6.3 有效性
当以下任何一项为真时,头部无效:
-
消息的字节数少于包含完整头部所需的数量。所需数量由 PSM 定义。
-
其协议值与 PROTOCOL_RTPS2 的值不匹配。
-
主要协议版本大于实现支持的主要协议版本。
8.3.6.4 接收器状态的变化
接收器的初始状态在 8.3.4 中描述。本小节描述新消息的头部如何影响接收器的状态。
Receiver.sourceGuidPrefix = Header.guidPrefix
Receiver.sourceVersion = Header.version
Receiver.sourceVendorId = Header.vendorId
Receiver.haveTimestamp = false
8.3.6.5 逻辑解释
无