Skip to content

1. 背景与问题定义

在 DDSI/RTPS 协议架构中,参与者(Participant)及其端点(Writer/Reader)的发现过程是相互独立且异步进行的。该过程主要依赖以下两个协议:

  • SPDP (Simple Participant Discovery Protocol):用于发现远程参与者。
  • SEDP (Simple Endpoint Discovery Protocol):用于发现远程端点(写入者或读取者)。

由于网络环境的复杂性(如延迟、丢包、多播到达时序差异),通信双方极易进入**不对称发现(Asymmetric Discovery)**状态。即一方已完成对另一方的发现,而反向发现尚未完成。

mermaid
sequenceDiagram
    participant W as 写入者 Writer(节点A)
    participant R as 读取者 Reader(节点B)

    R-->>W: ✅ Reader 已发现 Writer(收到 SPDP+SEDP)
    Note over W: ❌ Writer 尚未发现 Reader\n(SPDP/SEDP 包丢失或延迟)

    R->>W: 请求数据(已建立单侧匹配)
    W-->>R: ???(Writer 不知道这个 Reader 存在)

这种状态若未加管控,将引发以下核心问题:

1.1 数据交付风险

在缺乏严格状态同步的实现中,若读取者(Reader)已发现写入者(Writer),可能会尝试接收数据。此时若写入者尚未感知到该读取者的存在,可能导致:

  • 无效数据传输:写入者可能向未确认的端点发送数据,造成带宽浪费。
  • 安全隐患:读取者可能接收来自未经过完整匹配验证(如 QoS 不匹配)的源的数据。
mermaid
graph LR
    W_legit[合法写入者] -->|正常数据| R[读取者]
    W_fake[恶意/错误写入者] -->|伪造数据| R
    R -->|无法区分| PROBLEM[❌ 数据安全问题]

1.2 通信阻塞风险

当处于“半连接”状态时(Reader 认为 Writer 存在并等待数据,但 Writer 认为 Reader 不存在而不发送数据),应用层调用 read()take() 接口时可能陷入无限期阻塞。这是因为接收端在等待一个发送端认为“不需要发送”的数据流。

mermaid
sequenceDiagram
    participant App as 应用层
    participant R as 读取者
    participant W as 写入者

    App->>R: 调用 read() / take()
    R->>R: 等待来自"已发现Writer"的数据
    Note over W: Writer 以为 Reader 不存在\n从不发送数据
    R-->>App: ⏳ 永远等待... 无限期阻塞

2. 核心解决方案:代理模型(Proxy Model)

Cyclone DDS 通过引入**代理(Proxy)**机制来解决上述不对称性问题。其核心设计原则是:本地实体仅与已知的远程实体镜像(即代理)进行交互。

mermaid
graph TB
    subgraph 节点A 本地视角
        W[本地写入者 Writer]
        PR[代理读取者 Proxy Reader\n🪞 远程Reader的本地镜像]
        W -- "只向已知代理发送数据" --> PR
    end

    subgraph 节点B 远程
        R[真实读取者 Reader]
    end

    subgraph 节点B 本地视角
        R2[本地读取者 Reader]
        PW[代理写入者 Proxy Writer\n🪞 远程Writer的本地镜像]
        PW -- "数据来自已知代理" --> R2
    end

    PR -. "网络传输" .-> R
    W -. "网络传输" .-> PW

2.1 基本定义

对于每一个检测到的远程实体,本地节点会在内存中创建一个对应的代理对象。该代理作为远程实体的本地镜像,代表其参与所有的匹配计算、状态管理及数据传输控制。

  • Proxy Participant:对应远程节点的镜像。
  • Proxy Writer / Proxy Reader:对应远程端点的镜像。

2.2 通信约束

代理模型强制执行以下通信规则:

  1. 发送限制:本地写入者(Writer)仅向已建立 Proxy Reader 的远程端点发送数据。
  2. 接收限制:本地读取者(Reader)仅接收来自已建立 Proxy Writer 的远程端点的数据。

通过这一机制,只有当双向发现均完成(即双方都建立了对方实体的代理)后,实际的数据传输通道才会被激活。

3. 工作原理与流程

3.1 代理创建与匹配流程

mermaid
flowchart TD
    A[节点B 上线] --> B[广播 SPDP 消息\n宣告参与者存在]
    B --> C{节点A 收到 SPDP}
    C -- 是 --> D[节点A 创建\n代理参与者 Proxy Participant]
    D --> E[节点B 通过 SEDP 宣告端点\nWriter/Reader 信息]
    E --> F{节点A 收到 SEDP}
    F -- 是 --> G[节点A 创建\n代理端点 Proxy Writer/Reader]
    G --> H{进行匹配检查\nTopic + Type + QoS}
    H -- 匹配成功 --> I[✅ 建立完整通信连接]
    H -- 不匹配 --> J[❌ 不建立连接\n代理存在但不通信]

代理的生命周期遵循严格的层级创建逻辑:

  1. 参与者发现

    • 节点收到远程节点的 SPDP 消息。
    • 创建 Proxy Participant
    • 启动租约(Lease Duration)计时器。
  2. 端点发现

    • 节点收到远程端点的 SEDP 消息。
    • 在对应的 Proxy Participant 下创建 Proxy WriterProxy Reader
  3. 匹配检查

    • 系统检查本地端点与新创建的代理是否满足匹配条件(Topic 名称、数据类型、QoS 策略)。
    • 仅当匹配成功且双向代理均存在时,才建立逻辑连接并允许数据流动。

3.2 解决不对称发现的机制

mermaid
sequenceDiagram
    participant W as 写入者 (节点A)
    participant PA as 节点A 内部状态
    participant PB as 节点B 内部状态
    participant R as 读取者 (节点B)

    Note over PA,PB: 阶段一:节点B先发现节点A
    R->>PB: SPDP收到 → 创建 Proxy Participant(A)
    R->>PB: SEDP收到 → 创建 Proxy Writer(W)
    Note over PB: ✅ PW(W)已存在\n但W尚未创建PR(R)

    Note over W,PA: 阶段二:节点A尚未发现节点B
    W->>PA: 查询已知代理读取者列表
    PA-->>W: ❌ 列表为空,无PR(R)
    W->>W: 不发送任何数据(防护)

    Note over PA,PB: 阶段三:节点A完成发现
    W->>PA: SPDP收到 → 创建 Proxy Participant(B)
    W->>PA: SEDP收到 → 创建 Proxy Reader(R)
    PA->>PA: 匹配检查:Topic✅ Type✅ QoS✅
    PA-->>W: ✅ PR(R)已创建,可以发送

    W->>R: 开始发送数据(双侧匹配完成)

代理模型通过状态机消除了时间窗口带来的不确定性:

  • 场景:节点 B 发现了节点 A 的 Writer,但节点 A 尚未发现节点 B 的 Reader。
  • 状态
    • 节点 B 创建了 Proxy Writer(A),但节点 A 内部尚无 Proxy Reader(B)
    • 节点 A 的 Writer 查询本地代理列表,发现无对应的 Proxy Reader(B)
  • 结果:节点 A 的 Writer 不发送任何数据。直到节点 A 收到节点 B 的 SEDP 消息并创建 Proxy Reader(B) 后,通信才会开始。

此机制确保了不会出现“一端等待而另一端沉默”的死锁情况,同时也防止了数据发送给未经验证的接收者。

3.3 多播模式的特殊处理

mermaid
graph LR
    W[写入者] -- "单播\nUnicast" --> PR1[代理读取者1\n✅ 已发现]
    W -- "单播\nUnicast" --> PR2[代理读取者2\n✅ 已发现]
    W -- "多播\nMulticast\n(可选)" --> ALL[所有读取者\n包括未发现的]
    
    ALL --> R_known[已知读取者\n✅ 正常接收]
    ALL --> R_unknown[未知读取者\n⚠️ 可接收但Writer不追踪\n无可靠性保证]

在单播模式下,代理模型执行严格的白名单机制。在多播模式下:

  • 写入者可将数据发送至多播组。
  • 未建立代理的读取者理论上可能接收到多播数据包。
  • 处理策略:虽然物理上可能收到数据,但在逻辑层面上,由于缺乏对应的 Proxy Writer,这些数据通常会被上层中间件丢弃或不进行处理,且不享受可靠性保障(如重传机制)。多播在此被视为一种可选的降级或优化手段,不改变代理模型对连接状态管理的核心逻辑。

4. 生命周期管理与资源回收

代理模型采用层级化的资源管理机制,确保分布式系统中的资源能够及时释放,避免“僵尸”连接积累。

mermaid
graph TD
    subgraph Proxy Hierarchy 代理层级
        PP["代理参与者\nProxy Participant\n(对应远程节点)"]
        PW1["代理写入者\nProxy Writer A"]
        PW2["代理写入者\nProxy Writer B"]
        PR1["代理读取者\nProxy Reader A"]
        PP --> PW1
        PP --> PW2
        PP --> PR1
    end

    LEASE["租约管理\nLease Duration\n(SPDP 中声明)"]
    PP -- "心跳续租" --> LEASE
    LEASE -- "超时未续" --> DELETE["🗑️ 删除代理参与者\n级联删除所有子代理端点"]

    SEDP_D["SEDP Dispose/Un-register\n(端点主动删除)"] --> DELETE_EP["🗑️ 仅删除对应代理端点"]

4.1 层级结构

text
Proxy Participant (根节点)
├── Proxy Writer 1
├── Proxy Writer 2
└── Proxy Reader 1

4.2 销毁触发条件

触发事件 操作行为 影响范围
SEDP Dispose/Unregister 删除特定的代理端点 仅删除对应的 Proxy Writer/Reader
Lease Duration 超时 判定远程参与者失联 级联删除:移除 Proxy Participant 及其下所有子端点代理
显式下线通知 接收特定控制消息 立即清理相关代理资源,无需等待超时

租约机制(Lease Mechanism)
SPDP 消息中包含租约时长声明。远程参与者需周期性发送心跳(SPDP 消息)以续租。若本地节点在指定时间内未收到心跳,则判定远程节点失效,自动触发级联清理,防止资源泄漏。

5. 方案对比与总结

相较于无代理或弱状态管理的实现,Cyclone DDS 的代理模型在以下维度表现出显著优势:

维度 朴素实现/无状态模式 Cyclone DDS 代理模型
连接建立条件 单侧发现即可尝试通信 双侧代理建立后方可通信
数据安全性 存在接收未验证数据的风险 仅处理来自已知代理的数据
阻塞风险 半连接状态易导致无限等待 代理缺失时主动抑制发送/接收,避免死锁
资源管理 依赖全局超时,清理滞后 层级级联删除 + 租约机制,清理精确及时
匹配严谨性 可能存在误匹配 执行 Topic + Type + QoS 三重验证

结论

Cyclone DDS 的代理模型本质上是通过在本地构建远程实体的状态镜像,将分布式的异步发现过程转化为本地的确定性状态机管理。该方法严格界定了“可见性”与“可通信性”的边界,从根本上消除了因网络异步性导致的数据一致性问题、安全漏洞及资源泄漏风险。

基于 VitePress 构建