RTPS协议之PSM:UDP/IP
目录
- 映射RTPS类型
- GUID
- GuidPrefix_t
- EntityId_t
- 预先定义的EntityIds
- GUID_t
- Submessages中或内置Topic数据的类型映射
- Time_t
- Duration_t
- Locator_t
- GroupDigest_t
- RTPS消息映射
- SubmessageElements的映射
- SerializedPayload
- Count
将协议PIM映射到UDP/IP之上的平台特定模型(PSM)。此PSM的目标是以最小的开销直接在UDP/IP之上提供映射。
UDP/IP作为DDS应用程序传输层的适用性源于以下几个因素:
- 普遍可用性。作为IP堆栈的核心部分,UDP/IP几乎在所有的操作系统上都可用。
- 轻量级。UDP/IP是一个非常简单的协议,在IP之上添加的服务最少。其使用使得能够在开销最小的情况下利用基于IP的网络。
- Best-effort。UDP/IP提供best-effort的服务,这很好地符合了许多实时数据流的质量服务需求。在需要可靠传输的情况下,RTPS协议提供了在UDP的best-effort服务基础上实现reliable delivery的机制。
- 无连接。UDP/IP提供无连接的服务;这允许多个RTPS端点共享一个操作系统UDP资源(套接字/端口),同时允许消息有效地交错发送,实质上为每个单独的数据流提供了带外机制。
- 可预测的行为。与TCP不同,UDP不会引入导致操作在不同时间内阻塞的计时器。因此,使用UDP对实时应用的影响更易于建模。
- 可扩展性和组播支持。UDP/IP原生支持组播,这允许单条消息高效地分发给大量接收者。
映射RTPS类型
GUID
GuidPrefix_t
GUID作为作为RTPS Entities的属性,唯一标识DDS domain中每个entity。GUID由12个字节的GuidPrefix_t和4字节的EntityId_t组成。
PSM将GuidPrefix_t映射为如下结构体:
typedef octet GuidPrefix_t[12];
PIM定义的预留常量GUIDPREFIX_UNKNOWN映射如下:
#define GUIDPREFIX_UNKNOWN {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}
EntityId_t
EntityId_t唯一标识Participant中的Endpoint。PSM映射EntityId_t到如下的数据结构体中:
typedef octet OctetArray3[3]; struct {
OctetArray3 entityKey; octet entityKind;
};
预留常量ENTITYID_UNKNOWN在PIM中被映射为:
#define ENTITYID_UNKNOWN {{0x00, 0x00, 0x00}, 0x00}
EntityId_t中的entityKind字段(一个字节)编码了实体的类型(参与者、读取者、写入者、读取者组、写入者组),以及该实体是否为内置实体(由协议完全预定义,自动实例化)、用户定义的实体(由协议定义,但仅由用户根据应用程序需求实例化),或者是供应商特定的实体(由特定于供应商的协议扩展定义,因此可以被其他供应商的实现忽略)。
当不是预先定义的情况下(详见下文),只要生成的EntityId_t在参与者内是唯一的,EntityId_t中的entityKey字段可以由中间件实现任意选择。
对象是否为内置实体、供应商特定实体或用户定义实体的信息被编码在entityKind的两个最高有效位中。这两个比特位设置为:
• ‘00’ 表示用户定义的实体。
• ‘11’ 表示内置实体。
• ‘01’ 表示供应商特定的实体。
实体类型的详细信息被编码在entityKind字段的最后六个bit位中。下表提供了在协议版本2.4中支持的entityKind可能值的完整列表。这些值在这个主版本(2)中是固定的。在协议的更高次要版本中可能会添加新的实体类型,以便扩展实体模型。
Kind of Entity | User-defined Entity | Built-in Entity |
---|---|---|
unknown | 0x00 | 0xc0 |
Participant | N/A | 0xc1 |
Writer (with Key) | 0x02 | 0xc2 |
Writer (no Key) | 0x03 | 0xc3 |
Reader (no Key) | 0x04 | 0xc4 |
Reader (with Key) | 0x07 | 0xc7 |
Writer Group | 0x08 | 0xc8 |
Reader Group | 0x09 | 0xc9 |
预先定义的EntityIds
RTPS协议都会给内置的entities定义entity IDs。
PIM 指定参与者的 EntityId_t 有预定义的值 ENTITYID_PARTICIPANT。所有预定义的实体 ID 对应的 PSM 映射出现在下表中 - 由 RTPS 协议完全预定义的 EntityId_t 值中。在这个主版本(2)中,这些实体 ID 的含义不会改变,但是未来的次要版本可能会增加额外的保留实体 ID。
Entity | entityId_t |
---|---|
participant | ENTITYID_PARTICIPANT = {{00,00,01},c1} |
SEDPbuiltinTopicWriter | ENTITYID_SEDP_BUILTIN_TOPICS_ANNOUNCER = {{00,00,02},c2} |
SEDPbuiltinTopicReader | ENTITYID_SEDP_BUILTIN_TOPICS_DETECTOR = {{00,00,02},c7} |
SEDPbuiltinPublicationsWriter | ENTITYID_SEDP_BUILTIN_PUBLICATIONS_ANNOUNCER = {{00,00,03},c2} |
SEDPbuiltinPublicationsReader | ENTITYID_SEDP_BUILTIN_PUBLICATIONS_DETECTOR = {{00,00,03},c7} |
SEDPbuiltinSubscriptionsWriter | ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_ANNOUNCER = {{00,00,04},c2} |
SEDPbuiltinSubscriptionsReader | ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_DETECTOR = {{00,00,04},c7} |
SPDPbuiltinParticipantWriter | ENTITYID_SPDP_BUILTIN_PARTICIPANT_ANNOUNCER = {{00,01,00},c2} |
SPDPbuiltinParticipantReader | ENTITYID_SPDP_BUILTIN_PARTICIPANT_DETECTOR = {{00,01,00},c7} |
BuiltinParticipantMessageWriter | ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER = |
{{00,02,00},c2} | |
BuiltinParticipantMessageReader | ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER = {{00,02,00},c7} |
GUID_t
子条款 8.2.4 规定所有具有 DomainParticipant 的 RTPS 实体共享相同的 guidPrefix。此外,8.2.4.2 节指出,实现者在选择 guidPrefix 时有一定的自由度,只要在同一 DDS 域内的每个 DomainParticipant 都具有唯一的 guidPrefix。PIM(平台独立模型)对此自由度进行了限制。
为了符合此规范,RTPS 协议的实现应当设置 guidPrefix 的前两个字节以匹配其分配的 vendorId(见 8.3.3.1.3 节)。这确保了即使使用多种协议实现,guidPrefix 在 DDS 域内仍然保持唯一性。换句话说,RTPS 协议的实现可以自由选择任何适当的技术来生成 guidPrefix 的唯一值,只要它们满足以下约束:
guidPrefix[0] = vendorId[0] guidPrefix[1] = vendorId[1]
这两个字节的值按上述规定设定,唯一目的是为了使不同实现之间能够生成唯一的 guidPrefix。该值不应被用于其他目的。这样可以确保这种改变不会破坏与协议先前版本的互操作性。
关于保留 vendorId 的使用进一步描述见 9.4.4 节。
由 PIM 定义的保留常量 GUID_UNKNOWN 映射为:
#define GUID_UNKNOWN { GUIDPREFIX_UNKNOWN, ENTITYID_UNKNOWN }
这里表示一个未知的 GUID(全局唯一标识符)。
Submessages中或内置Topic数据的类型映射
Time_t
时间的表示遵循 IETF 网络时间协议 (NTP) 标准(IETF RFC 1305)。在这种表示方法中,时间以秒和秒的小数部分的形式表示,使用以下公式:
time = seconds +(小数部分 / 2^32)
时间的起始点由保留值 TIME_ZERO 表示,对应于 UNIX 时间纪元 1970 年 1 月 1 日的零时。
这段描述说明了 NTP 协议中时间的具体表示方式及其起始点。
Duration_t
时间的表示遵循 IETF 网络时间协议 (NTP) 标准(IETF RFC 1305)中定义的方法。在这种表示方法中,时间以秒和秒的小数部分的形式表示,公式如下:
time = seconds +(小数部分 / 2^32)
RTPS 规范的 2.4 版本之前的版本没有明确规定 Duration_t 的表示方法,因此实现时应该考虑到供应商和协议版本,以便正确解读这些字段。
Locator_t
如果 Locator_t 的类型是 LOCATOR_KIND_UDPv4,则地址包含一个 IPv4 地址。在这种情况下,地址的前 12 个字节必须为零。最后 4 个字节用于存储 IPv4 地址。IPv4 地址的点分十进制表示法“a.b.c.d”与其在 Locator_t 地址字段中的表示之间的映射关系如下:
address = (0,0,0,0,0,0,0,0,0,0,0,0,a,b,c,d}
如果 Locator_t 的类型是 LOCATOR_KIND_UDPv6,则地址包含一个 IPv6 地址。IPv6 地址通常使用简写的十六进制表示法,这种表示法与地址字段中的 16 个字节一一对应。例如,IPv6 地址“FF00:4501:0:0:0:0:0:32”的表示如下:
address = (0xff,0,0x45,0x01,0,0,0,0,0,0,0,0,0,0,0,0x32}
Locator_t 类型的范围被划分为以下几个区间:
- 0x00000003 至 0x01ffffff(包括两端)被保留给特定厂商的 Locator_t 类型使用,并且不会被任何未来的 RTPS 协议版本所使用。
- 0x02000000 至 0x02ffffff(包括两端)被保留供 RTPS 规范未来使用。
- 0x03000000 及以上被保留给第三方开发的传输层 Locator_t 类型使用(即既不是特定厂商也不是特定协议),并且不会被任何未来的 RTPS 协议版本所使用。
GroupDigest_t
这个类型用于表示同一个Participants中的Entities group,数据结构的定义如下:
typedef octet OctetArray3[3];
struct {
OctetArray3 entityKey;
octet entityKind;
};
struct EntityIdSet_t {
sequence<EntityId_t> entityIds;
};
在构建 entityIds 序列时,值按照 EntityId_t 的递增顺序排列。为了进行排序,4 字节的 EntityId_t 被重新解释为一个小端序编码的 32 位有符号整数(IDL4 的 int32 基本类型)。
GroupDigest_t 是通过对 EntityIdSet_t 结构体的 CDR 大端序序列化应用 128 位 MD5 摘要算法(IETF RFC 1321)来计算得出的。GroupDigest_t 是 MD5 摘要的前 4 个字节。
空组由 GroupDigest_t 的零值表示。它并不是通过对空序列进行哈希运算来计算得出的。
RTPS消息映射
下图定义了一个Message消息,由一个Header和若干Submessages组成。
一条消息具有一个众所周知的长度。这个长度并不会被 RTPS 协议显式地发送,而是作为消息传输的基础协议的一部分。在 UDP/IP 的情况下,消息的长度即是 UDP 有效载荷的长度。
SubmessageElements的映射
SerializedPayload
SerializedPayload子消息元素包含了应用程序定义的数据对象值或唯一标识该数据对象的键值的序列化表示。
用于将应用级数据类型编码为序列化字节流的过程规范并不严格属于RTPS协议的一部分。但是,为了实现互操作性,所有实现都必须使用一致的表示形式(具体请参考这里:TODO 序列化负载表示)。
Count
PSM将RTPS SubmessageElements中定义的Count SubmessageElement映射到如下结构:
typedef Count_t Count;
遵循CDR编码规则,Count SubmessageElement在网络传输中的表示形式为: