DDSI-RTPS V2.3版本规范中文翻译 part4--行为模块3

8.4.10 RTPS Reader参考实现

RTPS Reader参考实现基于RTPS Reader类的特化,首次引入于8.2。本子条款描述了RTPS Reader及用于建模RTPS Reader参考实现的所有附加类。实际行为在8.4.11和8.4.12中描述。

8.4.10.1 RTPS Reader

RTPS Reader是RTPS Endpoint的特化,表示从一个或多个RTPS Writer端点接收CacheChange消息的参与者。参考实现StatelessReader和StatefulReader是RTPS Reader的特化,在它们对匹配的Writer端点保留的知识方面存在差异。

图8.21 - RTPS Reader端点

RTPS Reader的配置属性列在表8.62中,允许对协议行为进行精细调整。RTPS Reader的操作列在表8.63中。

表8.62 - RTPS Reader配置属性

RTPS Reader:RTPS端点

属性

类型

含义

与DDS关系

heartbeatResponseDelay

Duration_t

协议调整参数,允许RTPS Reader延迟发送肯定或否定的确认(见8.4.12.2)。

heartbeatSuppressionDuration

Duration_t

协议调整参数,允许RTPS Reader忽略在收到先前的HEARTBEAT之后“太快”到达的HEARTBEAT。

reader_cache

History Cache

包含此RTPS Reader的CacheChange更改历史记录。

expectsInlineQos

bool

指定RTPS Reader是否期望随数据一起发送内联QoS。

表 8.63 - RTPS 读取器操作

RTPS Reader操作

操作名称

参数列表

类型

new

<返回值>

Reader

属性值

Reader 和所有超类所需的属性值集合

8.4.10.1.1 默认时间相关值

以下时间相关的值被用作默认设置,以便于在实现之间实现“开箱即用”的互操作性。

 

heartbeatResponseDelay.sec = 0; heartbeatResponseDelay.nanosec = 500 * 1000 * 1000; // 500 milliseconds heartbeatSuppressionDuration.sec = 0; heartbeatSuppressionDuration.nanosec = 0;

8.4.10.1.2 new 操作

此操作创建一个新的 RTPS 读取器。

新创建的读取器 this 初始化如下:

  • this.guid:根据构造函数中指定的内容初始化。

  • this.unicastLocatorList:根据构造函数中指定的内容初始化。

  • this.multicastLocatorList:根据构造函数中指定的内容初始化。

  • this.reliabilityLevel:根据构造函数中指定的内容初始化。

  • this.topicKind:根据构造函数中指定的内容初始化。

  • this.expectsInlineQos:根据构造函数中指定的内容初始化。

  • this.heartbeatResponseDelay:根据构造函数中指定的内容初始化。

  • this.reader_cache:初始化为一个新的历史缓存(HistoryCache)。

8.4.10.2 RTPS StatelessReader

RTPS StatelessReader 是 RTPS Reader 的一个特化版本。RTPS StatelessReader 不了解匹配写入者的数量,也不为每个匹配的 RTPS Writer 维护任何状态。

在当前的参考实现中,StatelessReader 并没有添加任何配置属性或操作,这些都是从 Reader 超类继承而来的。因此,这两个类实际上是相同的。虚拟机使用表 8.64 中的操作与 StatelessReader 交互。

表 8.64 - StatelessReader 操作

StatelessReader 操作

操作名称

参数列表

参数类型

new

<返回值>

StatelessReader

属性值

StatelessReader 及所有超类所需的属性值集合

8.4.10.2.1 new 操作

这个操作创建一个新的 RTPS StatelessReader。其初始化与 RTPS Reader 超类上的执行相同。

8.4.10.3 RTPS StatefulReader

RTPS StatefulReader 是 RTPS Reader 的特化形式。它会保持每个匹配的 RTPS Writer 的状态,这些状态在 RTPS WriterProxy 类中维护。

表 8.65 - RTPS StatefulReader 属性

RTPS StatefulReader: RTPS Reader

属性

类型

含义

与 DDS 的关系

matched_writers

WriteProxy[*]

用于维护与 Reader 匹配的远程 Writers 的状态。

N/A

虚拟机使用以下操作与 StatefulReader 交互:

表 8.66 - StatefulReader 操作

StatefulReader 操作

操作名称

参数列表

参数类型

new

<返回值>

StatefulReader

属性值

StatefulReader 及所有超类所需的属性值集合

matched_writer_add

<返回值>

void

a_writer_proxy

WriterProxy

matched_writer_remove

<返回值>

void

a_writer_proxy

WriterProxy

matched_writer_lookup

<返回值>

WriterProxy

a_writer_guid

GUID_t

8.4.10.3.1 new 操作

此操作创建一个新的 RTPS StatefulReader。新创建的有状态读取器 this 如下初始化:

  • this.attributes:按照构造函数中指定的内容初始化。

  • this.matched_writers:初始为空。

8.4.10.3.2 matched_writer_add 操作

此操作将 WriterProxy a_writer_proxy 添加到 StatefulReader 的 matched_writers 中。

  • 执行操作:ADD a_writer_proxy TO {this.matched_writers};

8.4.10.3.3 matched_writer_remove 操作

此操作从 StatefulReader 的 matched_writers 中移除 WriterProxy a_writer_proxy

  • 执行操作:

  • REMOVE a_writer_proxy FROM {this.matched_writers};

  • delete a_writer_proxy;

8.4.10.3.4 matched_writer_lookup 操作

此操作在 StatefulReader 的 matched_writers 中查找具有 GUID_t a_writer_guid 的 WriterProxy。

  • 执行操作:FIND proxy IN this.matched_writers SUCH-THAT (proxy.remoteWriterGuid == a_writer_guid); return proxy;

8.4.10.4 RTPS WriterProxy

RTPS WriterProxy 代表 RTPS StatefulReader 为每个匹配的 RTPS Writer 维护的信息。RTPS WriterProxy 的属性在表 8.67 中描述。

表 8.67 - RTPS WriterProxy 属性

RTPS WriterProxy

属性

类型

含义

与 DDS 的关系

remoteWriterGuid

GUID_t

标识匹配的写入者。

N/A

unicastLocatorList

Locator_t[*]

单播(地址,端口)组合列表,可用于向匹配的写入者发送消息。列表可能为空。

N/A

multicastLocatorList

Locator_t[*]

多播(地址,端口)组合列表,可用于向匹配的写入者发送消息。列表可能为空。

N/A

dataMaxSizeSerialized

long

表示匹配的写入者可能发送的任何 SerializedPayload 的最大大小的可选属性。

N/A

changes_from_writer

CacheChange[*]

从匹配的 RTPS Writer 接收或预期接收的 CacheChange 更改列表。

N/A

remoteGroupEntityId

EntityId_t

标识匹配的读取者所属的组。

DDS 订阅者的 EntityId

虚拟机使用表 8.68 中的操作与 WriterProxy 交互。

表 8.68 - WriterProxy 操作

WriterProxy 操作

操作名称

参数列表

参数类型

new

<返回值>

WriterProxy

属性值

WriterProxy 所需的属性值集合

available_changes_max

<返回值>

SequenceNumber_t

irrelevant_change_set

<返回值>

void

a_seq_num

SequenceNumber_t

lost_changes_update

<返回值>

void

first_available_seq_num

SequenceNumber_t

missing_changes

<返回值>

SequenceNumber_t[]

missing_changes_update

<返回值>

void

last_available_seq_num

SequenceNumber_t

received_change_set

<返回值>

void

a_seq_num

SequenceNumber_t

8.4.10.4.1 new 操作

此操作创建一个新的 RTPS WriterProxy。新创建的 writer proxy this 初始化如下:

  • this.attributes:根据构造函数中指定的内容初始化。

  • this.changes_from_writer:初始化为包含来自该 Writer 代表的 WriterProxy 的所有过去和未来的样本。

changes_from_writer 的新创建的 WriterProxy 被初始化为包含来自该 Writer 代表的 WriterProxy 的所有过去和未来的样本。这只是一个概念性的表示,用于描述 Stateful 参考实现。changes_from_writer 中每个 CacheChange 的 ChangeFromWriter 状态初始化为 UNKNOWN,表示 StatefulReader 最初不知道这些更改是否实际已经存在。如在 8.4.12.3 中讨论的,状态将随着 StatefulReader 接收到实际更改或通过 HEARTBEAT 消息被告知它们的存在而变为 RECEIVED 或 MISSING。

8.4.10.4.2 available_changes_max 操作

此操作返回 RTPS WriterProxy 中可供 DDS DataReader 访问的 changes_from_writer 更改中最大的 SequenceNumber_t。

使任何 CacheChange a_change 对 DDS DataReader "可访问" 的条件是,没有比 a_change.sequenceNumber 更小或相等的来自 RTPS Writer 的更改,其状态为 MISSING 或 UNKNOWN。换句话说,available_changes_max 以及所有之前的更改要么是 RECEIVED,要么是 LOST。

虚拟机中的逻辑动作:

 

seq_num := MAX { change.sequenceNumber SUCH-THAT ( change IN this.changes_from_writer AND ( change.status == RECEIVED OR change.status == LOST) ) }; return seq_num;

8.4.10.4.3 irrelevant_change_set 操作

此操作修改 ChangeFromWriter 的状态,以指示序列号为 a_seq_num 的 CacheChange 对 RTPS Reader 来说是无关的。

虚拟机中的逻辑动作:

 

FIND change FROM this.changes_from_writer SUCH-THAT (change.sequenceNumber == a_seq_num); change.status := RECEIVED; change.is_relevant := FALSE;

8.4.10.4.4 lost_changes_update 操作

此操作修改 WriterProxy 中任何状态为 MISSING 或 UNKNOWN 并且序列号小于 first_available_seq_num 的更改的 ChangeFromWriter 中存储的状态。这些更改的状态被修改为 LOST,表明这些更改在 RTPS Writer 代表的 WriterHistoryCache 中不再可用。

虚拟机中的逻辑动作:

 

FOREACH change IN this.changes_from_writer SUCH-THAT ( change.status == UNKNOWN OR change.status == MISSING AND seq_num < first_available_seq_num ) DO { change.status := LOST; }

8.4.10.4.5 missing_changes 操作

此操作返回 WriterProxy 中状态为 MISSING 的更改子集。状态为 MISSING 的更改代表在 RTPS Writer 代表的 WriterHistoryCache 中可用但尚未被 RTPS Reader 接收的更改集合。

返回值:

 

return { change IN this.changes_from_writer SUCH-THAT change.status == MISSING};

8.4.10.4.6 missing_changes_update 操作

此操作修改 WriterProxy 中任何状态为 UNKNOWN 并且序列号小于或等于 last_available_seq_num 的更改的 ChangeFromWriter 中存储的状态。这些更改的状态从 UNKNOWN 修改为 MISSING,表明这些更改在 RTPS Writer 代表的 WriterHistoryCache 中可用但尚未被 RTPS Reader 接收。

虚拟机中的逻辑动作:

 

FOREACH change IN this.changes_from_writer SUCH-THAT ( change.status == UNKNOWN AND seq_num <= last_available_seq_num ) DO { change.status := MISSING; }

8.4.10.4.7 received_change_set 操作

此操作修改指代序列号为 a_seq_num 的 CacheChange 的 ChangeFromWriter 的状态。更改的状态设置为 RECEIVED,表示它已被接收。

虚拟机中的逻辑动作:

 

FIND change FROM this.changes_from_writer SUCH-THAT change.sequenceNumber == a_seq_num; change.status := RECEIVED

8.4.10.5 RTPS ChangeFromWriter

RTPS ChangeFromWriter 是一个关联类,它在 RTPS Reader HistoryCache 中维护 CacheChange 的信息,这与 WriterProxy 代表的 RTPS Writer 相关。RTPS ChangeFromWriter 的属性在表 8.69 中描述。

表 8.69 - RTPS ChangeFromWriter 属性

RTPS ReaderProxy

属性

类型

含义

与 DDS 的关系

status

ChangeFromWriter StatusKind

表示相对于 WriterProxy 代表的 RTPS Writer 的 CacheChange 的状态。

N/A. 由协议使用。

is_relevant

bool

表示更改是否与 RTPS Reader 相关。不相关更改的确定受 DDS DataReader TIME_BASED_FILTER QoS 和 DDS ContentFilteredTopics 的使用影响。

-

8.4.11 RTPS 无状态读取器行为

8.4.11.1 best-effort 无状态读取器行为

最佳尝试 RTPS 无状态读取器的行为不依赖于任何写入者,其描述见图 8.22。

图 8.22 - 最佳尝试无状态读取器的行为

状态机转换列在表 8.70 中。

表 8.70 - 最佳尝试无状态读取器行为的转换

转换状态

事件

下一个状态

T1

初始 RTPS 读取器被创建

等待

T2

等待状态下接收到 DATA 消息

等待

T3

等待状态下 RTPS 读取器被删除

最终

8.4.11.1.1 转换 T1

由创建一个 RTPS 无状态读取器 the_rtps_reader 触发。这是根据 8.2.9 中描述的 DDS DataReader 的创建而产生的。 虚拟机中没有逻辑动作。

8.4.11.1.2 转换 T2

由 RTPS 读取器 the_rtps_reader 接收到 DATA 消息触发。DATA 消息包含更改 a_change。其表示方法在 8.3.7.2 中描述。 无状态读取器的无状态特性使其无法维护确定来自原始 RTPS 写入者的最高序列号所需的信息。结果是,在这些情况下,相应的 DDS DataReader 可能会呈现重复或乱序更改。注意,如果 DDS DataReader 配置为按“源时间戳”排序数据,则在通过 DDS DataReader 访问数据时,任何可用数据仍将按顺序呈现。 如 8.4.3 所述,实际的无状态实现可能会尝试避免这种限制并以非永久方式维护此信息(例如使用在一定时间后过期信息的缓存),尽可能地接近于如果维护状态所产生的行为。 虚拟机中执行以下逻辑动作:

 

a_change := new CacheChange(DATA); the_rtps_reader.reader_cache.add_change(a_change);

8.4.11.1.3 转换 T3

由 RTPS 读取器 the_rtps_reader 的销毁触发。这是根据 8.2.9 中描述的 DDS DataReader 的销毁而产生的。 虚拟机中没有逻辑动作。

8.4.11.2 可靠的无状态读取器行为

RTPS 协议不支持此组合。为了实现可靠协议,RTPS 读取器必须在每个匹配的 RTPS 写入者上保持一些状态。

8.4.12 RTPS 有状态读取器行为

8.4.12.1 best-effort有状态读取器行为

best-effort RTPS 有状态读取器相对于每个匹配的写入者的行为在图 8.23 中描述。

图8.23 - 对于每个匹配的写入者,最大努力型有状态读取器的行为

状态机转换在表8.71中列出。

表8.71 - 对于每个匹配的写入者,最大努力型有状态读取器行为的转换

转换状态

当前状态

事件

下一个状态

T1

初始

RTPS读取器配置与匹配的RTPS写入器

等待

T2

等待

收到匹配写入器的DATA消息

等待

T3

等待

RTPS读取器配置为不再与RTPS写入器匹配

终止

T4

等待

收到GAP消息

等待

8.4.12.1.1 转换 T1

这个转换由一个RTPS读取器'the_rtps_reader'与匹配的RTPS写入器的配置触发。这个配置是通过发现协议(8.5节)完成的,作为发现与'the_rtps_reader'相关的DDS DataReader匹配的DDS DataWriter的结果。

发现协议为WriterProxy构造器参数提供了值。转换在虚拟机中执行以下逻辑操作:

 

plaintextCopy code a_writer_proxy := new WriterProxy(remoteWriterGuid, remoteGroupEntityId, unicastLocatorList, multicastLocatorList); the_rtps_reader.matched_writer_add(a_writer_proxy);

WriterProxy使用来自写入器的所有过去和未来样本进行初始化,如8.4.10.4节所讨论的。

8.4.12.1.2 转换 T2

这个转换由RTPS读取器'the_rtps_reader'接收到一个DATA消息触发。DATA消息包含变化'a_change'。表示在8.3.7.2节中描述。

最大努力读取器检查与变化相关联的序列号是否严格大于从这个RTPS写入器过去接收到的所有变化的最高序列号(WP::available_changes_max())。如果这个检查失败,RTPS读取器将丢弃这个变化。这确保了没有重复的变化和没有乱序的变化。

转换在虚拟机中执行以下逻辑操作:

 

a_change := new CacheChange(DATA); writer_guid := {Receiver.SourceGuidPrefix, DATA.writerId}; writer_proxy := the_rtps_reader.matched_writer_lookup(writer_guid); expected_seq_num := writer_proxy.available_changes_max() + 1; if ( a_change.sequenceNumber >= expected_seq_num ) { the_rtps_reader.reader_cache.add_change(a_change); writer_proxy.received_change_set(a_change.sequenceNumber); if ( a_change.sequenceNumber > expected_seq_num ) { writer_proxy.lost_changes_update(a_change.sequenceNumber); } }

在转换之后,以下后置条件成立:

writer_proxy.available_changes_max() >= a_change.sequenceNumber

8.4.12.1.3 转换 T3

这个转换由RTPS读取器'the_rtps_reader'的配置触发,使其不再与由WriterProxy 'the_writer_proxy'代表的RTPS写入器匹配。这个配置是通过发现协议(8.5节)完成的,作为打破一个DDS DataWriter与与'the_rtps_reader'相关的DDS DataReader的预先存在的匹配的结果。

转换在虚拟机中执行以下逻辑操作:

 

the_rtps_reader.matched_writer_remove(the_writer_proxy); delete the_writer_proxy;

8.4.12.1.4 转换 T4

这个转换由接收到发送给RTPS StatefulReader 'the_reader'并由WriterProxy 'the_writer_proxy'代表的RTPS写入器的GAP消息触发。

转换在虚拟机中执行以下逻辑操作:

 

FOREACH seq_num IN [GAP.gapStart, GAP.gapList.base-1] DO { the_writer_proxy.irrelevant_change_set(seq_num); } FOREACH seq_num IN GAP.gapList DO { the_writer_proxy.irrelevant_change_set(seq_num); }

8.4.12.2 可靠的有状态读取器行为

关于每个匹配的RTPS写入器的可靠RTPS有状态读取器的行为在图8.24中描述。

图8.24 - 对于每个匹配的写入者,可靠的有状态读取器的行为

状态机转换在表8.72中列出。

表8.72 - 对于匹配的写入者的可靠读取器行为的转换

转换编号

当前状态

事件

下一个状态

T1

初始1

RTPS读取器配置与匹配的RTPS写入器

等待

T2

等待

收到心跳(HEARTBEAT)消息

如果(HB.FinalFlag == NOT_SET)则必须发送确认,否则如果(HB.LivelinessFlag == NOT_SET)则可能发送确认,否则保持等待

T3

可能发送确认

守卫条件:WP::missing_changes() == <空>

等待

T4

可能发送确认

守卫条件:WP::missing_changes() != <空>

必须发送确认

T5

必须发送确认

after(R::heartbeatResponseDelay)

等待

T6

初始2

RTPS读取器配置与匹配的RTPS写入器

准备

T7

准备

收到心跳(HEARTBEAT)消息

准备

T8

准备

收到数据(DATA)消息

准备

T9

准备

收到间隙(GAP)消息

准备

T10

任何状态

RTPS读取器配置为不再与RTPS写入器匹配

最终

8.4.12.2.1 转换 T1

这个转换由一个RTPS可靠的有状态读取器'the_rtps_reader'与匹配的RTPS写入器的配置触发。这个配置是通过发现协议(8.5)作为发现与'the_rtps_reader'相关的DDS DataReader匹配的DDS DataWriter的结果而完成的。

发现协议为WriterProxy构造器参数提供了值。转换在虚拟机中执行以下逻辑操作:

 

a_writer_proxy := new WriterProxy(remoteWriterGuid, remoteGroupEntityId, unicastLocatorList, multicastLocatorList); the_rtps_reader.matched_writer_add(a_writer_proxy);

WriterProxy使用来自写入器的所有过去和未来样本进行初始化,如8.4.10.4节所讨论的。

8.4.12.2.2 转换 T2

这个转换由RTPS有状态读取器'the_reader'从代表WriterProxy 'the_writer_proxy'的RTPS写入器接收到的心跳(HEARTBEAT)消息触发。

转换在虚拟机中不执行任何逻辑操作。但需要注意的是,接收到心跳消息会引发并发的转换T7(8.4.12.2.7),该转换执行逻辑操作。

8.4.12.2.3 转换 T3

这个转换由守卫条件[W::missing_changes() == <空>]触发,表明代表WriterProxy的RTPS写入器的HistoryCache中的所有已知变化都已被RTPS读取器接收。

转换在虚拟机中不执行任何逻辑操作。

8.4.12.2.4 转换 T4

这个转换由守卫条件[W::missing_changes() != <空>]触发,表明有一些已知的变化在代表WriterProxy的RTPS写入器的HistoryCache中,但尚未被RTPS读取器接收。

转换在虚拟机中不执行任何逻辑操作。

8.4.12.2.5 转换 T5

这个转换由一个计时器触发,表明自进入状态must_send_ack以来,R::heartbeatResponseDelay的持续时间已过。

转换为WriterProxy 'the_writer_proxy'在虚拟机中执行以下逻辑操作:

 

missing_seq_num_set.base := the_writer_proxy.available_changes_max() + 1; missing_seq_num_set.set := <空>; FOREACH change IN the_writer_proxy.missing_changes() DO ADD change.sequenceNumber TO missing_seq_num_set.set; send ACKNACK(missing_seq_num_set);

以上逻辑操作并未表达ACKNACK消息的PSM映射在容纳序列号方面的容量限制。在ACKNACK消息无法容纳完整的丢失序列号列表的情况下,它应该被构造为包含具有较小序列号值的子集。

8.4.12.2.6 转换 T6

类似于T1(8.4.12.2.1),这个转换由一个RTPS可靠的有状态读取器'the_rtps_reader'与匹配的RTPS写入器的配置触发。

转换在虚拟机中不执行任何逻辑操作。

8.4.12.2.7 转换 T7

这个转换由RTPS有状态读取器'the_reader'从代表WriterProxy 'the_writer_proxy'的RTPS写入器接收到的心跳(HEARTBEAT)消息触发。

转换在虚拟机中执行以下逻辑操作:

 

the_writer_proxy.missing_changes_update(HEARTBEAT.lastSN); the_writer_proxy.lost_changes_update(HEARTBEAT.firstSN);

8.4.12.2.8 转换 T8

这个转换由RTPS有状态读取器'the_reader'从代表WriterProxy 'the_writer_proxy'的RTPS写入器接收到的数据(DATA)消息触发。

转换在虚拟机中执行以下逻辑操作:

 

a_change := new CacheChange(DATA); the_reader.reader_cache.add_change(a_change); the_writer_proxy.received_change_set(a_change.sequenceNumber);

任何过滤都是在使用DDS DataReader的读取或取操作访问数据时进行的,如8.2.9节所述。

8.4.12.2.9 转换 T9

这个转换由RTPS有状态读取器'the_reader'从代表WriterProxy 'the_writer_proxy'的RTPS写入器接收到的间隙(GAP)消息触发。

转换在虚拟机中执行以下逻辑操作:

 

FOREACH seq_num IN [GAP.gapStart, GAP.gapList.base-1] DO { the_writer_proxy.irrelevant_change_set(seq_num); } FOREACH seq_num IN GAP.gapList DO { the_writer_proxy.irrelevant_change_set(seq_num); }

8.4.12.2.10 转换 T10

这个转换由RTPS读取器'the_rtps_reader'的配置触发,使其不再与WriterProxy 'the_writer_proxy'代表的RTPS写入器匹配。这个配置是通过发现协议(8.5)完成的,作为打破一个DDS DataWriter与与'the_rtps_reader'相关的DDS DataReader的预先存在的匹配的结果。

转换在虚拟机中执行以下逻辑操作:

 

the_rtps_reader.matched_writer_remove(the_writer_proxy); delete the_writer_proxy;

8.4.12.3 ChangeFromWriter说明

ChangeFromWriter跟踪与特定远程RTPS写入器相关的每个CacheChange的通信状态(属性status)和相关性(属性is_relevant)。

在图8.24中描述的RTPS有状态读取器的行为作为协议操作的副作用修改了每个ChangeFromWriter。为了进一步定义协议,查看表示任何给定ChangeFromWriter的status属性值的状态机是有启发性的。这在图8.25中为可靠的有状态读取器显示。最大努力的有状态读取器只使用状态图的一个子集。

图8.25 - ChangeFromWriter中status属性值的变化

这些状态有以下含义:

  • <Unknown>:序列号为SequenceNumber_t seq_num的ACacheChange可能在RTPS写入器中已经可用,也可能尚未可用。

  • <Missing>:序列号为SequenceNumber_t seq_num的CacheChange在RTPS写入器中可用,但RTPS读取器尚未接收到。

  • <Requested>:序列号为SequenceNumber_t seq_num的CacheChange已经从RTPS写入器请求,响应可能正在等待或进行中。

  • <Received>:序列号为SequenceNumber_t seq_num的CacheChange已经被接收:如果seq_num对RTPS读取器来说是相关的,则作为DATA;如果seq_num是无关的,则作为GAP。

  • <Lost>:序列号为SequenceNumber_t seq_num的CacheChange在RTPS写入器中不再可用。它将不会被接收。

以下描述了触发状态机中转换的主要事件。请注意,这个状态机仅跟踪特定ChangeForReader的‘status’属性,并不执行任何特定动作,也不发送任何消息。

  • new ChangeFromWriter(seq_num):WriterProxy已创建一个ChangeFromWriter关联类来跟踪序列号为SequenceNumber_t seq_num的CacheChange的状态。

  • received HB(firstSN <= seq_num <= lastSN):读取器接收到一个HEARTBEAT,其中HEARTBEAT.firstSN <= seq_num <= HEARTBEAT.lastSN,表明该序列号的CacheChange可以从RTPS写入器获取。

  • sent NACK(seq_num):读取器已发送包含seq_num的ACKNACK消息,在ACKNACK.readerSNState中,表明RTPS读取器尚未接收到CacheChange,并请求再次发送。

  • received GAP(seq_num):读取器接收到一个GAP消息,其中seq_num在GAP.gapList中,这意味着seq_num对RTPS读取器来说是无关的。

  • received DATA(seq_num):读取器接收到一个DATA消息,其中DATA.sequenceNumber == seq_num。

  • received HB(firstSN > seq_num):读取器接收到一个HEARTBEAT,其中HEARTBEAT.firstSN > seq_num,表明该序列号的CacheChange在RTPS写入器中不再可用。

8.4.13 写入器生命力协议

DDS规范要求有一个生命力机制。RTPS通过写入器生命力协议实现了这一要求。写入器生命力协议定义了两个参与者之间所需的信息交换,以断言参与者所包含的写入器的生命力。

为了实现互操作性,所有实现都必须支持写入器生命力协议。

8.4.13.1 通用方法

写入器生命力协议使用预定义的内置端点。使用内置端点意味着一旦参与者知道另一个参与者的存在,它就可以假设远程参与者提供的内置端点的存在,并与本地匹配的内置端点建立关联。

用于内置端点间通信的协议与用于应用定义端点的协议相同。

8.4.13.2 写入器生命力协议所需的内置端点

写入器生命力协议所需的内置端点是BuiltinParticipantMessageWriter和BuiltinParticipantMessageReader。这些端点的名称反映了它们是通用的事实。这些端点用于生命力,但未来可以用于其他数据。

RTPS协议为这些内置端点保留以下EntityId_t的值:

  • ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER

  • ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER 每个EntityId_t实例的实际值由每个PSM定义。

8.4.13.3 BuiltinParticipantMessageWriter和BuiltinParticipantMessageReader的QoS

为了互操作性,BuiltinParticipantMessageWriter和BuiltinParticipantMessageReader都应使用以下QoS值:

  • durability.kind = TRANSIENT_LOCAL_DURABILITY

  • history.kind = KEEP_LAST_HISTORY_QOS

  • history.depth = 1 BuiltinParticipantMessageWriter应使用reliability.kind = RELIABLE_RELIABILITY_QOS。 BuiltinParticipantMessageReader可以配置为使用RELIABLE_RELIABILITY_QOS或BEST_EFFORT_RELIABILITY_QOS。如果BuiltinParticipantMessageReader配置为使用BEST_EFFORT_RELIABILITY_QOS,则ParticipantProxy::builtinEndpointQos中的BEST_EFFORT_PARTICIPANT_MESSAGE_DATA_READER标志应被设置。 如果ParticipantProxy::builtinEndpointQos包含在SPDPdiscoveredParticipantData中,则BuiltinParticipantMessageWriter应根据标志指示的方式处理BuiltinParticipantMessageReader。如果没有包含ParticipantProxy::builtinEndpointQos,则BuiltinParticipantMessageWriter应将BuiltinParticipantMessageReader视为配置了RELIABLE_RELIABILITY_QOS。

8.4.13.4 与写入器生命力协议使用的内置端点相关的数据类型

每个RTPS端点都有一个HistoryCache,用于存储与端点关联的数据对象的变化。这对于RTPS内置端点也是如此。因此,每个RTPS内置端点都依赖于某种数据类型,该数据类型代表写入其HistoryCache的数据的逻辑内容。

图8.26定义了与RTPS内置端点相关的ParticipantMessageData数据类型,用于DCPSParticipantMessage主题。

图8.26 参与者消息数据

8.4.13.5 使用BuiltinParticipantMessageWriter和BuiltinParticipantMessageReader实现写入器生命力协议

通过向BuiltinParticipantMessageWriter写入样本来声明属于参与者的一部分写入器的生命力。如果参与者包含一个或多个具有AUTOMATIC_LIVELINESS_QOS生命力的写入器,则应以比这些写入器共享的最小租约持续时间更快的速率写入一个样本。类似地,如果参与者包含一个或多个具有MANUAL_BY_PARTICIPANT_LIVELINESS_QOS生命力的写入器,则应以比这些写入器的最小租约持续时间更快的速率写入一个单独的样本。这两个实例在目的上是正交的,因此如果一个参与者包含每种生命力类型的写入器,那么两个单独的实例必须定期写入。这些实例使用它们的DDS密钥来区分,该密钥包括participantGuidPrefix和kind字段。通过此协议处理的这两种类型的生命力QoS将导致唯一的kind字段,因此在HistoryCache中形成两个不同的实例。

在这两种生命力情况下,participantGuidPrefix字段包含写入数据(因此声明其写入器的生命力)的参与者的GuidPrefix_t。

DDS生命力种类MANUAL_BY_TOPIC_LIVELINESS_QOS不是使用BuiltinParticipantMessageWriter和BuiltinParticipantMessageReader实现的。它在8.7.2.2.3中讨论。

8.4.14 可选行为

这个小节描述了RTPS协议的可选特性。可选特性可能不被所有RTPS实现支持。可选特性不影响基本的互操作性,但仅在所有涉及的实现都支持它时才可用。

8.4.14.1 大数据

如7.6节所述,RTPS对底层传输提出的要求非常少。传输提供能够最大努力发送数据包的无连接服务就足够了。

尽管如此,传输可能会施加自己的限制。例如,它可能限制最大数据包大小(例如,UDP的64K)以及因此限制最大RTPS子消息大小。这主要影响Data子消息,因为它限制了serializedData的最大大小或者所使用的数据类型的最大序列化大小。

为了解决这个限制,8.3.7引入了以下子消息以启用大数据片段:

  • DataFrag

  • HeartbeatFrag

  • NackFrag 以下小节列出了互操作性所需的相应行为。

8.4.14.1.1 如何选择片段大小

片段大小由写入器确定,必须满足以下要求:

  • 写入器可用的所有传输必须能够容纳至少包含一个片段的DataFragSubmessages。这意味着具有最小最大消息大小的传输决定了片段大小。

  • 片段大小对于给定的写入器必须是固定的,并且对于所有远程读取器是相同的。通过固定片段大小,片段编号所指的数据不依赖于特定的远程读取器。这简化了来自读取器的负面确认(NackFrag)的处理。

  • 片段大小必须满足:片段大小 <= 65536字节。 注意片段大小是由写入器可用的所有传输决定的,而不仅仅是达到所有当前已知读取器所需的传输子集。这确保新发现的读取器,无论它们可以通过哪种传输到达,都可以被容纳,而不必改变片段大小,这将违反上述要求。

8.4.14.1.2 如何发送片段

如果需要片段化,则Data子消息被一系列DataFrag子消息替换。发送DataFrag子消息的协议行为与发送常规Data子消息的行为相匹配,但有以下额外要求:

  • DataFrag子消息按顺序发送,其中顺序由递增的片段编号定义。注意这并不保证按顺序到达。

  • 只有在需要时才对数据进行片段化。如果写入器可用的多个传输中有些不需要片段化,则应在这些传输上发送常规Data子消息。同样,对于可变大小的数据类型,如果特定序列号不需要片段化,则必须使用常规Data子消息。

  • 对于给定的序列号,如果使用内联QoS参数,则它们必须包含在第一个DataFrag子消息中(包含片段编号等于1的片段)。它们也可以包含在此序列号的后续DataFrag子消息中,但这不是必需的。 如果传输能够容纳给定片段大小的多个片段,建议实现将尽可能多的片段连接成单个DataFrag消息。 发送多个DataFrag消息时,可能需要流量控制以避免淹没网络。可能的方法包括漏桶或令牌桶流量控制方案。这不是RTPS规范的一部分。

8.4.14.1.3 如何重新组装片段

DataFrag子消息包含重新组装序列化数据所需的所有信息。一旦接收到所有片段,与常规Data子消息相同的协议行为适用。

请注意,实现必须能够处理DataFrag子消息的乱序到达。

8.4.14.1.4 可靠通信

可靠发送DataFrag子消息的协议行为与发送常规Data子消息的行为相匹配,但有以下额外要求:

  • Heartbeat子消息的语义保持不变:Heartbeat消息只能包括所有片段都可用的序列号。

  • AckNack子消息的语义保持不变:AckNack消息只能在接收到该序列号的所有片段时才能肯定确认序列号。同样,只有在所有片段都丢失时,序列号才能被否定确认。

  • 为了否定确认给定序列号的一部分片段,必须使用NackFrag子消息。当数据片段化时,Heartbeat可能触发AckNack和NackFrag子消息。 额外考虑:

  • 如上所述,Heartbeat子消息只能在该序列号的所有片段都可用时才能包括序列号。如果写入器想通知读取器关于给定序列号的片段的部分可用性,则可以改用HeartbeatFrag子消息。对于非常大的数据以及使用流量控制时,片段级别的可靠性可能会有所帮助。

  • NackFrag子消息只能作为对Heartbeat或HeartbeatFrag子消息的响应发送。

8.4.15 实现指南

这个小节的内容不是协议正式规范的一部分。这个小节的目的是为协议的高性能实现提供指南。

8.4.15.1 ReaderProxy和WriterProxy的实现

PIM将ReaderProxy建模为与写入器的HistoryCache中的每个CacheChange保持关联。这种关联被建模为通过关联类ChangeForReader进行调解。这个模型的直接实现将导致为每个ReaderProxy维护大量信息。实际上,所需的是ReaderProxy能够实现协议使用的操作,这并不要求使用显式关联。

例如,unsent_changes()和next_unsent_change()操作可以通过让ReaderProxy维护一个单一的序列号‘highestSeqNumSent’来实现。highestSeqNumSent将记录发送到ReaderProxy的任何CacheChange的序列号的最高值。使用这个,unsent_changes()操作可以通过查找HistoryCache中的所有更改并选择序列号大于highestSeqNumSent的更改来实现。next_unsent_change()的实现也会查看HistoryCache并返回具有高于highestSeqNumSent的下一个最高序列号的CacheChange。如果HistoryCache按序列号维护索引,则这些操作可以高效完成。

可以使用相同的技术来实现requested_changes()、requested_changes_set()和next_requested_change()。在这种情况下,实现可以维护一组序列号的滑动窗口(可以通过SequenceNumber_t lowestRequestedChange和固定长度的位图有效表示)来存储特定序列号是否当前被请求。窗口中不适合的请求可以被忽略,因为它们对应于窗口中序列号更高的序列号,读取器可以依靠稍后重新发送请求,如果它仍然缺少更改。

可以使用类似的技术来实现acked_changes_set()和unacked_changes()。

8.4.15.2 Gap和AckNack子消息的高效使用

Gap和AckNack子消息都被设计成可以包含一组序列号的信息。为了简单起见,协议描述中使用的虚拟机并不总是试图充分利用这些子消息来存储它们适用的所有序列号。结果可能是有时会发送多个Gap或AckNack消息,而更高效的实现会将这些子消息合并成一个。所有这些实现都符合协议并且可以互操作。然而,合并多个Gap和AckNack子消息并利用这些子消息包含一组序列号的能力的实现,在带宽和CPU使用方面会更有效。

8.4.15.3 合并多个Data子消息

RTPS协议允许将多个子消息合并成一个RTPS消息。这意味着它们将共享一个RTPS头部并在单个“网络传输事务”中发送。大多数网络传输与消息中额外字节的额外成本相比,有相对较大的固定开销。因此,将子消息合并成单个RTPS消息的实现通常会更好地利用CPU和带宽。

一个特别常见的情况是将多个Data子消息合并成单个RTPS消息。这种需要可能发生在响应请求多个更改的AckNack,或者作为写入器端进行的多个更改尚未传播到读取器的结果。在所有这些情况下,通常将子消息合并成更少的RTPS消息是有益的。

请注意,合并Data子消息不限于来自同一RTPS写入器的子消息。也可以合并来自多个RTPS写入器实体的子消息。对应于同一DDS发布者所属的DDS DataWriter实体的RTPS写入器实体是这种操作的主要候选者。

8.4.15.4 捎带HeartBeat子消息

RTPS协议允许将不同种类的子消息合并成单个RTPS消息。一个特别有用的案例是在Data子消息之后捎带HeartBeat子消息。这允许RTPS写入器明确请求对其发送的更改的确认,而无需发送单独的HeartBeat所需的额外流量。

8.4.15.5 向未知的 readerId 发送

如消息模块所述,可以发送未指定 readerId(ENTITYID_UNKNOWN)的 RTPS 消息。这在通过多播发送这些消息时是必需的,但也允许通过单播向同一参与者中的多个读者发送单个消息。鼓励实现使用此功能以最小化带宽使用。

8.4.15.6 从无响应的读者中回收有限资源

实现可能只有有限的资源可供使用。对于写者来说,当所有读者都已确认队列中的一个样本,并且资源限制规定旧的样本条目将用于新样本时,应该回收队列资源。

可能会出现活跃的读者变得无响应并且永远不会确认写者的情况。与其在无响应的读者上阻塞,不如允许写者将读者视为“不活跃”并继续更新其队列。读者的状态要么是活跃的,要么是不活跃的。活跃的读者已发送最近收到的 ACKNACK。写者应根据接收到的 ACKNACK 的速率和数量使用一种机制来确定读者的不活跃性。然后,可以释放所有活跃读者已确认的样本,如果必要,写者可以回收这些资源。请注意,当读者变为不活跃时,无法保证严格的可靠性。

8.4.15.7 在心跳、心跳片段、AckNack 和 NackFrag 子消息中设置计数

心跳的计数元素用于区分逻辑心跳。接收到的具有与先前接收到的心跳相同的计数的心跳可以被忽略,以防止触发重复的修复会话。因此,实现应确保相同的逻辑心跳带有相同的计数。

读者接收到的心跳的计数应大于同一写者的所有较旧心跳的计数。否则可以丢弃它们。只要满足这个要求,实现可以决定写者是否为每个读者保持一个特定的计数,或者计数在其所有匹配的读者之间共享。ACKNACK 的计数也适用相同的逻辑。实现可以决定读者是否为每个写者保持一个特定的计数,或者在其所有匹配的写者之间共享。

为了适应整数溢出,计数元素应根据模运算规则递增和比较。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值