1.3 核心源码导读与流程拆解
章节状态:已完成
更新时间:2026-05-21
一、生活化通俗类比
类比:餐厅点餐系统
想象你去一家大型连锁餐厅点餐:
Discovery(发现阶段)
- 你走进商场,看到多家餐厅(Participant Discovery)
- 你选择一家,服务员给你菜单(Topic Discovery)
- 菜单上的菜品就是Topic,如"宫保鸡丁"、"鱼香肉丝"
- 你确认这家有你想吃的菜(Endpoint Discovery)
Data Flow(数据流)
- 你下单(write):服务员记录 → 传给厨房 → 厨师做菜
- 上菜(read):厨师完成 → 传菜员 → 送到你桌上
QoS(服务质量)
- 普通餐厅:尽力而为,可能上错或漏单(Best-Effort)
- 高档餐厅:必达承诺,每道菜确认(Reliable)
- 快餐店:只保留最近10单(History Depth=10)
Transport(传输方式)
- 堂食:店内传送带(Shared Memory,最快)
- 外卖:电动车(UDP,快但可能丢)
- 重要文件:专车送达(TCP,可靠但慢)
二、核心模块与商用场景
场景1:Discovery机制(自动发现)
2.1.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 大规模集群(1000+节点) | 快速发现、低带宽消耗 | 发现服务器模式(Discovery Server) |
| 跨网段部署 | NAT穿透、广域网发现 | TCP传输 + 发现服务器 |
| 动态扩缩容 | 节点即插即用 | SPDP/EDP自动发现协议 |
| 安全隔离 | 只发现授权节点 | DDS-Security + 域名隔离 |
2.1.2 核心流程
sequenceDiagram
autonumber
participant A as Participant A
新加入节点 participant PD as PDP
Participant Discovery participant EDP as EDP
Endpoint Discovery participant B as Participant B
已有节点 Note over A,B: === Participant Discovery === A->>PD: 发送Participant DATA(p) PD->>B: 转发A的信息 B->>PD: 发送Participant DATA(q) PD->>A: 转发B的信息 A->>B: 直接通信建立 Note over A,B: === Endpoint Discovery === A->>EDP: 发布DataWriter信息
(Topic: /vehicle/speed) EDP->>B: 查询匹配的DataReader B->>EDP: 返回匹配结果 EDP->>A: 通知匹配成功 Note over A,B: === 数据传输就绪 === A->>B: 开始发送数据
新加入节点 participant PD as PDP
Participant Discovery participant EDP as EDP
Endpoint Discovery participant B as Participant B
已有节点 Note over A,B: === Participant Discovery === A->>PD: 发送Participant DATA(p) PD->>B: 转发A的信息 B->>PD: 发送Participant DATA(q) PD->>A: 转发B的信息 A->>B: 直接通信建立 Note over A,B: === Endpoint Discovery === A->>EDP: 发布DataWriter信息
(Topic: /vehicle/speed) EDP->>B: 查询匹配的DataReader B->>EDP: 返回匹配结果 EDP->>A: 通知匹配成功 Note over A,B: === 数据传输就绪 === A->>B: 开始发送数据
2.1.3 源码定位
| 组件 | 源码路径 | 关键类 |
|---|---|---|
| PDP | src/cpp/rtps/builtin/discovery/participant/ |
PDP.cpp, PDPListener.cpp |
| EDP | src/cpp/rtps/builtin/discovery/endpoint/ |
EDP.cpp, EDPSimple.cpp |
| 发现数据 | src/cpp/rtps/builtin/data/ |
ParticipantProxyData.cpp, WriterProxyData.cpp |
2.1.4 gdb调试:完整案例(Discovery机制)
2.1.4.1 编译准备(Debug模式)
bash
# 1. 进入Fast-DDS目录
cd /home/my/code/opensource/Fast-DDS
# 2. 创建构建目录(如果不存在)
mkdir -p build && cd build
# 3. 配置Debug编译选项
# -DCMAKE_BUILD_TYPE=Debug: 生成调试符号
# -DCMAKE_CXX_FLAGS="-g -O0": 保留符号表,关闭优化
cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DCOMPILE_EXAMPLES=ON \
-DCMAKE_CXX_FLAGS="-g -O0 -fno-omit-frame-pointer"
# 4. 编译hello_world示例(使用多线程加速)
make hello_world -j$(nproc)
# 5. 验证编译结果(检查是否包含调试符号)
file ./examples/cpp/hello_world/hello_world
# 预期输出:... not stripped ...
# 6. 检查符号表
nm -C ./examples/cpp/hello_world/hello_world | grep "DomainParticipantFactory::create_participant" | head -5
# 预期输出:T eprosima::fastdds::dds::DomainParticipantFactory::create_participant(...)
编译选项说明:
| 选项 | 作用 | 为什么需要 |
|---|---|---|
-DCMAKE_BUILD_TYPE=Debug |
生成调试版本 | 保留调试符号,支持断点 |
-g |
生成调试信息 | gdb需要此信息定位源码 |
-O0 |
关闭编译优化 | 防止代码行错位,单步执行准确 |
-fno-omit-frame-pointer |
保留帧指针 | 方便栈回溯分析 |
2.1.4.2 启动gdb调试Discovery流程
bash
# 1. 启动gdb
gdb ./examples/cpp/hello_world/hello_world
# 2. 设置断点:Participant创建(Discovery起点)
(gdb) break eprosima::fastdds::dds::DomainParticipantFactory::create_participant
# 3. 设置断点:PDP发送发现消息
gdb> break eprosima::fastrtps::rtps::PDP::announce_participant_state
# 4. 设置断点:PDP接收发现消息
gdb> break eprosima::fastrtps::rtps::PDPListener::on_new_cache_change_added
# 5. 设置断点:EDP匹配Writer和Reader
gdb> break eprosima::fastrtps::rtps::EDP::pairing_writer_reader
# 6. 设置断点:匹配状态通知应用层
gdb> break eprosima::fastdds::dds::DataWriterListener::on_publication_matched
# 7. 运行程序
gdb> run
# 8. 程序会在第一个断点暂停,查看调用栈
gdb> bt
# 9. 查看局部变量
gdb> info locals
# 10. 查看Participant QoS配置
gdb> p participant_qos
# 11. 单步执行(进入函数内部)
gdb> step
# 12. 继续运行到下一个断点
gdb> continue
# 13. 当命中announce_participant_state时,查看发现消息内容
gdb> p participant_data
# 14. 查看Domain ID
gdb> p participant_data.m_domainId
# 15. 继续运行
gdb> continue
# 16. 当命中pairing_writer_reader时,查看匹配信息
gdb> p writer_guid
gdb> p reader_guid
# 17. 打印Topic名称
gdb> p topic_name
# 18. 继续运行直到程序结束或Ctrl+C
gdb> continue
2.1.4.3 常用gdb命令速查
| 命令 | 缩写 | 作用 | 使用场景 |
|---|---|---|---|
break <function> |
b |
设置断点 | 在函数入口暂停 |
run |
r |
运行程序 | 启动调试 |
continue |
c |
继续运行 | 跳到下一个断点 |
step |
s |
单步进入 | 进入函数内部 |
next |
n |
单步跳过 | 不进入函数内部 |
finish |
fin |
运行到函数返回 | 快速跳出当前函数 |
backtrace |
bt |
查看调用栈 | 了解代码调用路径 |
info locals |
i lo |
查看局部变量 | 检查当前上下文 |
print <var> |
p |
打印变量值 | 查看变量内容 |
display <var> |
disp |
持续显示变量 | 每次暂停自动显示 |
watch <var> |
wa |
监视变量变化 | 变量改变时暂停 |
list |
l |
显示源码 | 查看当前代码位置 |
info breakpoints |
i b |
查看断点列表 | 管理所有断点 |
delete <num> |
d |
删除断点 | 清除不需要的断点 |
quit |
q |
退出gdb | 结束调试 |
2.1.4.4 高级调试技巧
技巧1:条件断点(只在特定条件暂停)
bash
# 只在domain_id为0时暂停
gdb> break eprosima::fastdds::dds::DomainParticipantFactory::create_participant if domain_id == 0
# 只在特定Topic匹配时暂停
gdb> break eprosima::fastrtps::rtps::EDP::pairing_writer_reader if strcmp(topic_name, "/vehicle/speed") == 0
技巧2:多线程调试
bash
# 查看所有线程
gdb> info threads
# 切换到特定线程
gdb> thread 2
# 只运行当前线程
gdb> set scheduler-locking on
技巧3:保存和加载调试会话
bash
# 保存断点配置到文件
gdb> save breakpoints discovery.gdb
# 下次调试时加载
gdb> source discovery.gdb
2.1.4.5 其他场景调试参考
其他场景的调试步骤与Discovery类似,参考以下模板:
bash
# 1. 编译(同上,确保是Debug模式)
# 2. 启动gdb: gdb <可执行文件>
# 3. 设置对应场景的断点(见各场景断点列表)
# 4. run -> 观察 -> continue -> 分析
各场景关键断点快速参考:
| 场景 | 启动命令 | 关键断点 |
|---|---|---|
| SHM零拷贝 | gdb ./hello_world |
SharedMemTransport::send |
| DataSharing | gdb ./hello_world |
WriterPool::create_new_payload |
| Reliable传输 | gdb ./hello_world |
StatefulWriter::send_heartbeat |
| 分片传输 | gdb ./hello_world |
RTPSMessageGroup::add_data_frag |
| FlowControl | gdb ./FlowControlExample |
FlowController::schedule |
| Stateless/Stateful | gdb ./hello_world |
StatelessWriter::unsent_change_added |
| Liveliness | gdb ./hello_world |
LivelinessManager::assert_liveliness |
| Persistence | gdb ./hello_world |
SQLite3PersistenceService::add_persistent_sample |
2.1.4.6 Discovery机制调试(详细)
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
create_participant |
domain_id, participant_qos |
确认Domain隔离是否正确 |
announce_participant_state |
participant_guid, metatraffic_locators |
确认发现消息发送地址 |
on_new_cache_change_added |
change->writerGUID |
确认远程Participant身份 |
pairing_writer_reader |
writer_guid, reader_guid, topic_name |
确认Topic匹配逻辑 |
on_publication_matched |
info.current_count, info.last_subscription_handle |
确认匹配状态通知 |
2.1.5 对二次开发的启示
- 自定义发现协议 :继承
PDP类,实现自定义发现机制(如通过etcd/consul) - 发现过滤器 :在
EDP::pairing_writer_reader中加入自定义匹配逻辑 - 发现性能优化:减少发现消息频率,或使用发现服务器模式
场景2:Shared Memory传输(零拷贝)
2.2.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 高频传感器数据(激光雷达) | 微秒级延迟、零拷贝 | Shared Memory Transport |
| 大带宽数据(摄像头图像) | 避免内核态拷贝 | 共享内存 + 零拷贝序列化 |
| 同机多进程通信 | 低CPU占用、高吞吐 | SHM代替UDP回环 |
| 实时控制 | 确定性延迟 | SHM + 实时线程调度 |
2.2.2 核心流程
sequenceDiagram
autonumber
participant DW as DataWriter
participant SHM_W as SHM Writer
Segment participant Port as SHM Port
(/dev/shm/fastrtps_*) participant SHM_R as SHM Reader
Segment participant DR as DataReader Note over DW,DR: === 初始化阶段 === DW->>Port: 创建/打开共享内存端口 DR->>Port: 打开共享内存端口 Note over DW,DR: === 数据传输(零拷贝) === DW->>SHM_W: 1. 分配SHM缓冲区 DW->>SHM_W: 2. 直接写入数据(用户态) DW->>Port: 3. 写入端口描述符(通知Reader) Port->>DR: 4. 通知数据到达 DR->>SHM_R: 5. 直接读取数据(用户态) DR->>SHM_R: 6. 释放缓冲区 Note over DW,DR: === 关键:全程无内核拷贝 ===
Segment participant Port as SHM Port
(/dev/shm/fastrtps_*) participant SHM_R as SHM Reader
Segment participant DR as DataReader Note over DW,DR: === 初始化阶段 === DW->>Port: 创建/打开共享内存端口 DR->>Port: 打开共享内存端口 Note over DW,DR: === 数据传输(零拷贝) === DW->>SHM_W: 1. 分配SHM缓冲区 DW->>SHM_W: 2. 直接写入数据(用户态) DW->>Port: 3. 写入端口描述符(通知Reader) Port->>DR: 4. 通知数据到达 DR->>SHM_R: 5. 直接读取数据(用户态) DR->>SHM_R: 6. 释放缓冲区 Note over DW,DR: === 关键:全程无内核拷贝 ===
2.2.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| SHM Transport | src/cpp/rtps/transport/shared_mem/ |
SharedMemTransport.cpp |
| SHM管理 | src/cpp/rtps/transport/shared_mem/ |
SharedMemManager.cpp |
| SHM缓冲区 | src/cpp/rtps/transport/shared_mem/ |
SharedMemSegment.hpp |
| 当前IDE文件 | src/cpp/rtps/transport/shared_mem/ |
SharedMemTransportBak.h |
2.2.4 gdb调试:SHM零拷贝流程
调试目标:观察SHM如何分配、写入、通知、读取
bash
# 启动gdb
gdb ./examples/cpp/hello_world/hello_world
# 设置断点:SHM Transport初始化
gdb> break eprosima::fastdds::rtps::SharedMemTransport::init
# 设置断点:SHM发送(关键:观察零拷贝路径)
gdb> break eprosima::fastdds::rtps::SharedMemTransport::send
# 设置断点:SHM接收
gdb> break eprosima::fastdds::rtps::SharedMemTransport::receive
# 设置断点:缓冲区分配
gdb> break eprosima::fastdds::rtps::SharedMemManager::alloc_buffer
# 设置断点:端口打开(观察/dev/shm创建)
gdb> break eprosima::fastdds::rtps::SharedMemManager::open_port
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
init |
segment_name, segment_size, max_msg_size |
确认SHM段大小配置 |
send |
shm_handle, data_size, dest_locators |
确认数据写入SHM而非socket |
receive |
shm_handle, buffer_size |
确认从SHM读取数据 |
alloc_buffer |
buffer_size, segment->get_free_bytes() |
确认缓冲区分配策略 |
open_port |
port_name, port_address |
确认端口命名规则 |
系统级观察:
bash
# 终端2:监控共享内存文件创建
watch -n 0.5 'ls -la /dev/shm/ | grep fastrtps'
# 终端3:查看SHM段大小
df -h /dev/shm
# 终端4:监控进程打开的SHM文件
lsof -p $(pgrep hello_world) | grep /dev/shm
2.2.5 strace观察:SHM vs UDP差异
bash
# 观察SHM传输的系统调用(注意:send/recv阶段无read/write)
strace -f -e trace=openat,mmap,munmap,close,ftruncate \
./examples/cpp/hello_world/hello_world 2>&1 | grep -E "shm|/dev/shm"
# 预期输出:只有文件操作,没有socket发送
openat(AT_FDCWD, "/dev/shm/fastrtps_port7410", O_RDWR|O_CREAT) = 6
ftruncate(6, 1048576) = 0
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x7f1234567000
# 注意:没有sendto/recvfrom调用!
2.2.6 对二次开发的启示
- 自定义SHM策略 :继承
SharedMemTransport,实现环形缓冲区替代动态分配 - 大消息优化 :调整
max_msg_size和segment_size匹配业务需求 - 跨机SHM:结合RDMA技术,实现跨机零拷贝
场景3:Reliable传输(可靠传输)
2.3.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 金融交易数据 | 100%送达、顺序保证 | Reliable + KeepAll History |
| 工业控制指令 | 必达、超时检测 | Reliable + Deadline QoS |
| 文件传输 | 大文件可靠传输 | Reliable + 分片 + 重传 |
| 日志收集 | 不丢数据、可重传 | Reliable + Durability |
2.3.2 核心流程
sequenceDiagram
autonumber
participant W as Writer
participant HB as Heartbeat机制
participant N as 网络
participant ACK as ACK/NACK机制
participant R as Reader
Note over W,R: === 可靠传输流程 ===
W->>N: 发送Data(seq=1)
W->>HB: 启动Heartbeat定时器
alt Reader正常接收
N->>R: 接收Data(seq=1)
R->>ACK: 发送ACK(seq=1)
ACK->>W: 确认收到
W->>W: 从History移除seq=1
else Reader未收到(丢包)
W->>HB: Heartbeat超时
HB->>R: 发送Heartbeat(nack=seq=1)
R->>ACK: 发送NACK(seq=1)
ACK->>W: 请求重传
W->>N: 重传Data(seq=1)
N->>R: 接收Data(seq=1)
end
2.3.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| Reliable Writer | src/cpp/rtps/writer/ |
StatefulWriter.cpp |
| Heartbeat | src/cpp/rtps/writer/ |
StatefulWriter::send_heartbeat |
| ACK处理 | src/cpp/rtps/writer/ |
StatefulWriter::process_acknack |
| Reliable Reader | src/cpp/rtps/reader/ |
StatefulReader.cpp |
2.3.4 gdb调试:Reliable传输流程
调试目标:观察Heartbeat、ACK、重传机制
bash
# 启动gdb
gdb ./examples/cpp/hello_world/hello_world
# 设置断点:发送Heartbeat
gdb> break eprosima::fastrtps::rtps::StatefulWriter::send_heartbeat
# 设置断点:处理ACK/NACK
gdb> break eprosima::fastrtps::rtps::StatefulWriter::process_acknack
# 设置断点:数据重传
gdb> break eprosima::fastrtps::rtps::StatefulWriter::resend_data
# 设置断点:等待ACK确认
gdb> break eprosima::fastdds::dds::DataWriter::wait_for_acknowledgments
# 设置断点:Reader发送ACK
gdb> break eprosima::fastrtps::rtps::StatefulReader::send_acknack
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
send_heartbeat |
heartbeat_count, first_sn, last_sn |
确认心跳频率和范围 |
process_acknack |
ack_count, nack_count, nack_bitmap |
确认丢包检测 |
resend_data |
sequence_number, reader_guid |
确认重传逻辑 |
wait_for_acknowledgments |
acknowledgment_count, max_blocking_time |
确认阻塞等待机制 |
2.3.5 Wireshark分析:Reliable协议交互
bash
# 抓包
sudo tcpdump -i lo -w reliable.pcap portrange 7410-7415
# Wireshark过滤条件
# 查看Heartbeat
rtps.sm.id == 0x07
# 查看ACK
rtps.sm.id == 0x06
# 查看NACK
rtps.sm.id == 0x08
# 查看DATA消息
rtps.sm.id == 0x15 && rtps.data
2.3.6 对二次开发的启示
- 自定义重传策略 :修改
resend_data实现自适应重传(如根据RTT调整) - 批量ACK优化:修改ACK发送策略,减少小数据包数量
- 可靠性分级:实现部分可靠(如只保证关键消息)
场景4:分片传输(大消息处理)
2.4.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 高清图像传输 | 4K图像(10MB+) | 分片 + 可靠传输 |
| 点云数据 | 激光雷达一帧(100KB-1MB) | 分片 + SHM零拷贝 |
| 日志批量上传 | 大批量日志数据 | 分片 + 流控 |
| 固件升级 | 大文件可靠传输 | 分片 + 校验 |
2.4.2 核心流程
sequenceDiagram
autonumber
participant W as Writer
participant Frag as Fragmenter
participant N as 网络
participant Reas as Reassembler
participant R as Reader
Note over W,R: === 分片发送 ===
W->>Frag: 数据(100KB)
Frag->>Frag: 分片为10个10KB片段
loop 发送每个分片
Frag->>N: 发送DataFrag(seq=1, frag=1/10)
Frag->>N: 发送DataFrag(seq=1, frag=2/10)
Frag->>N: ...
Frag->>N: 发送DataFrag(seq=1, frag=10/10)
end
Note over W,R: === 分片重组 ===
N->>Reas: 接收frag=1/10
N->>Reas: 接收frag=2/10
N->>Reas: ...
Reas->>Reas: 缓存分片,检测完整性
Reas->>R: 完整数据(seq=1)
2.4.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| 分片发送 | src/cpp/rtps/messages/ |
RTPSMessageGroup::add_data_frag |
| 分片重组 | src/cpp/rtps/reader/ |
FragmentedChangePitStop.cpp |
| 分片缓存 | src/cpp/rtps/reader/ |
FragmentedChangePitStop.hpp |
2.4.4 gdb调试:分片传输流程
调试目标:观察分片如何生成、发送、缓存、重组
bash
# 启动gdb
gdb ./examples/cpp/hello_world/hello_world
# 设置断点:分片判断
gdb> break eprosima::fastrtps::rtps::RTPSMessageGroup::add_data
# 设置断点:分片发送
gdb> break eprosima::fastrtps::rtps::RTPSMessageGroup::add_data_frag
# 设置断点:分片接收
gdb> break eprosima::fastrtps::rtps::FragmentedChangePitStop::process
# 设置断点:分片完成通知
gdb> break eprosima::fastrtps::rtps::FragmentedChangePitStop::is_complete
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
add_data |
data_size, max_size, fragment_size |
确认分片触发条件 |
add_data_frag |
fragment_starting_num, fragments_in_submessage |
确认分片编号 |
process |
fragmented_change, received_fragments |
确认分片缓存状态 |
is_complete |
missing_fragments, fragment_count |
确认完整性检测 |
2.4.5 对二次开发的启示
- 动态分片大小:根据网络MTU动态调整分片大小
- 分片优先级:关键分片优先发送
- 并行传输:多路径并行传输不同分片
场景5:QoS策略(服务质量保证)
2.5.1 商用需求
| 商用场景 | 需求 | QoS组合 |
|---|---|---|
| 实时视频流 | 低延迟、允许丢帧 | Best-Effort + KeepLast(1) |
| 传感器融合 | 最新数据优先 | Best-Effort + KeepLast(5) |
| 控制指令 | 必达、顺序保证 | Reliable + KeepAll |
| 日志记录 | 持久化、不丢失 | Reliable + TransientLocal |
| 心跳检测 | 超时感知 | Liveliness + Deadline |
2.5.2 核心流程
graph TB
subgraph "QoS策略生效点"
direction TB
Write[DataWriter::write]
History[HistoryCache管理]
Send[Transport发送]
Receive[Transport接收]
Read[DataReader::read/take]
Write -->|ResourceLimits
max_samples| History History -->|Reliability
History depth| Send Send -->|LatencyBudget| Receive Receive -->|Deadline
Liveliness| Read end
max_samples| History History -->|Reliability
History depth| Send Send -->|LatencyBudget| Receive Receive -->|Deadline
Liveliness| Read end
2.5.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| QoS策略定义 | include/fastdds/dds/core/policy/ |
QosPolicies.hpp |
| History管理 | src/cpp/rtps/history/ |
WriterHistory.cpp, ReaderHistory.cpp |
| Deadline检测 | src/cpp/rtps/writer/ |
WriterPeriodicHeartbeat.cpp |
| Liveliness检测 | src/cpp/rtps/writer/ |
LivelinessManager.cpp |
2.5.4 gdb调试:QoS策略生效
调试目标:观察QoS如何在各阶段生效
bash
# 启动gdb
gdb ./examples/cpp/hello_world/hello_world
# 设置断点:History添加(观察ResourceLimits)
gdb> break eprosima::fastrtps::rtps::WriterHistory::add_change
# 设置断点:History移除(观察KeepLast)
gdb> break eprosima::fastrtps::rtps::WriterHistory::remove_min_change
# 设置断点:Deadline检测
gdb> break eprosima::fastrtps::rtps::WriterPeriodicHeartbeat::deadline_missed
# 设置断点:Liveliness检测
gdb> break eprosima::fastrtps::rtps::LivelinessManager::callback
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
add_change |
m_changes.size(), m_resource_limits |
确认资源限制生效 |
remove_min_change |
history_qos.kind, history_qos.depth |
确认History策略 |
deadline_missed |
deadline_qos.period, last_change_time |
确认Deadline检测 |
callback |
liveliness_qos.kind, lease_duration |
确认Liveliness状态 |
2.5.5 对二次开发的启示
- 动态QoS:运行时调整QoS策略
- QoS监控:统计QoS违规事件
- 自适应QoS:根据网络状况自动调整
三、调试工具速查表
3.1 gdb断点速查
| 场景 | 关键断点 | 观察目标 |
|---|---|---|
| Discovery | PDP::announce_participant_state EDP::pairing_writer_reader |
发现流程、匹配逻辑 |
| SHM零拷贝 | SharedMemTransport::send SharedMemTransport::receive |
零拷贝路径、缓冲区管理 |
| Reliable传输 | StatefulWriter::send_heartbeat StatefulWriter::process_acknack |
心跳、ACK、重传 |
| 分片传输 | RTPSMessageGroup::add_data_frag FragmentedChangePitStop::process |
分片生成、重组 |
| QoS策略 | WriterHistory::add_change WriterHistory::remove_min_change |
History管理、资源限制 |
| 数据流 | DataWriter::write DataReader::take_next_sample |
端到端数据流 |
3.2 strace观察重点
| 场景 | 命令 | 观察目标 |
|---|---|---|
| Discovery网络 | strace -e connect,sendto,recvfrom |
UDP发现消息 |
| SHM传输 | strace -e openat,mmap,ftruncate |
共享内存文件操作 |
| 性能分析 | strace -T -e sendto,recvfrom |
系统调用耗时 |
| 线程分析 | strace -e clone,pthread_create |
线程创建情况 |
3.3 Wireshark过滤条件
| 场景 | 过滤条件 | 观察目标 |
|---|---|---|
| Discovery | rtps.sm.id == 0x15 && rtps.participant |
SPDP发现消息 |
| Endpoint匹配 | rtps.sm.id == 0x15 && rtps.endpoint |
SEDP端点宣告 |
| Heartbeat | rtps.sm.id == 0x07 |
可靠性心跳 |
| ACK/NACK | `rtps.sm.id == 0x06 / | rtps.sm.id == 0x08` |
| 分片数据 | rtps.data_frag |
分片传输 |
四、问题定位决策树
graph TD
A[问题现象] --> B{连接问题?}
B -->|是| C[Discovery阶段]
C --> C1[gdb: PDP/EDP断点]
C --> C2[tcpdump: 抓SPDP消息]
C --> C3[检查Domain ID]
B -->|否| D{数据丢失?}
D -->|是| E[Reliable传输]
E --> E1[gdb: Heartbeat/ACK断点]
E --> E2[Wireshark: 分析ACK/NACK]
E --> E3[检查History QoS]
D -->|否| F{延迟高?}
F -->|是| G[传输层分析]
G --> G1[gdb: SHM send/receive]
G --> G2[strace -T: 系统调用耗时]
G --> G3[检查Transport配置]
F -->|否| H{内存问题?}
H --> I[资源管理]
I --> I1[gdb: History add/remove]
I --> I2[valgrind: 内存泄漏]
I --> I3[检查ResourceLimits]
场景6:DataSharing(数据共享)
2.6.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 超大带宽数据(8K视频) | 极致零拷贝、无内核参与 | DataSharing机制 |
| 多订阅者同机消费 | 一份数据多份读取 | 共享内存池 + 引用计数 |
| 实时性要求极高 | 微秒级延迟 | 无锁队列 + 通知机制 |
| 降低CPU占用 | 减少数据拷贝 | 完全零拷贝路径 |
2.6.2 核心流程
sequenceDiagram
autonumber
participant DW as DataWriter
participant WP as WriterPool
participant SHM as Shared Memory
participant RP as ReaderPool
participant DR as DataReader
Note over DW,DR: === 初始化 ===
DW->>WP: 创建WriterPool
DR->>RP: 创建ReaderPool
WP->>SHM: 映射共享内存段
RP->>SHM: 映射共享内存段
Note over DW,DR: === 数据发布(零拷贝) ===
DW->>WP: 获取空闲缓冲区
WP->>DW: 返回内存指针
DW->>SHM: 直接写入数据(用户态)
DW->>WP: 提交缓冲区(通知Reader)
WP->>RP: 通知数据到达
RP->>DR: 回调on_data_available
DR->>RP: 获取数据指针
RP->>DR: 返回内存指针
DR->>SHM: 直接读取数据(用户态)
DR->>RP: 释放缓冲区
Note over DW,DR: === 关键:全程无拷贝,无系统调用 ===
2.6.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| DataSharing | src/cpp/rtps/DataSharing/ |
DataSharingPayloadPool.cpp |
| WriterPool | src/cpp/rtps/DataSharing/ |
WriterPool.hpp |
| ReaderPool | src/cpp/rtps/DataSharing/ |
ReaderPool.hpp |
| 通知机制 | src/cpp/rtps/DataSharing/ |
DataSharingNotification.cpp |
2.6.4 gdb调试:DataSharing流程
调试目标:观察DataSharing如何实现完全零拷贝
bash
# 启动gdb
gdb ./examples/cpp/hello_world/hello_world
# 设置断点:WriterPool初始化
gdb> break eprosima::fastrtps::rtps::WriterPool::create_new_payload
# 设置断点:获取缓冲区
gdb> break eprosima::fastrtps::rtps::DataSharingPayloadPool::get_payload
# 设置断点:通知Reader
gdb> break eprosima::fastrtps::rtps::DataSharingNotification::notify
# 设置断点:Reader接收通知
gdb> break eprosima::fastrtps::rtps::DataSharingListener::on_data_available
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
create_new_payload |
segment_size, payload_size |
确认共享内存池大小 |
get_payload |
payload->data, payload->payload_owner |
确认零拷贝分配 |
notify |
notification_fd |
确认通知机制(eventfd/pipe) |
on_data_available |
pool->get_payload_count |
确认Reader获取数据 |
2.6.5 与SHM的区别
| 特性 | SHM Transport | DataSharing |
|---|---|---|
| 拷贝次数 | 1次(序列化到SHM) | 0次(直接写共享内存) |
| 序列化 | 需要 | 可选(支持PLAIN类型) |
| 适用场景 | 中等带宽 | 超大带宽、极低延迟 |
| 复杂度 | 低 | 高 |
2.6.6 对二次开发的启示
- 自定义内存池 :继承
DataSharingPayloadPool,实现特定对齐要求 - 通知机制优化:使用更高效的通知方式(如eventfd替代pipe)
- PLAIN类型优化:利用Fast-DDS的PLAIN类型实现完全零序列化
场景7:FlowControl(流量控制)
2.7.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 网络带宽受限 | 防止网络拥塞 | FlowController |
| 多优先级数据 | 高优先级优先发送 | 优先级队列 |
| 平滑流量 | 避免突发流量 | 令牌桶算法 |
| 带宽限制 | 限制最大发送速率 | ThroughputController |
2.7.2 核心流程
sequenceDiagram
autonumber
participant App as 应用
participant DW as DataWriter
participant FC as FlowController
participant Queue as 优先级队列
participant Token as 令牌桶
participant RTPS as RTPSWriter
Note over App,RTPS: === 数据写入 ===
App->>DW: write(data, priority=HIGH)
DW->>FC: schedule(data)
FC->>Queue: 按优先级入队
Note over App,RTPS: === 流量控制 ===
loop 定时器触发
Token->>Token: 生成令牌
Token->>FC: 通知可发送
end
Note over App,RTPS: === 数据发送 ===
FC->>Queue: 获取高优先级数据
Queue->>FC: 返回数据
FC->>Token: 消耗令牌
FC->>RTPS: 发送数据
2.7.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| FlowController | src/cpp/rtps/flowcontrol/ |
FlowControllerImpl.hpp |
| 优先级队列 | src/cpp/rtps/flowcontrol/ |
FlowControllerFactory.cpp |
| 令牌桶 | src/cpp/rtps/writer/ |
ThroughputController.cpp |
2.7.4 gdb调试:FlowControl流程
调试目标:观察流量控制如何限制发送速率
bash
# 启动gdb
gdb ./examples/cpp/flow_control/FlowControlExample
# 设置断点:数据调度
gdb> break eprosima::fastrtps::rtps::FlowController::schedule
# 设置断点:令牌桶检查
gdb> break eprosima::fastrtps::rtps::ThroughputController::process
# 设置断点:实际发送
gdb> break eprosima::fastrtps::rtps::FlowController::send
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
schedule |
priority, flow_controller_name |
确认优先级调度 |
process |
tokens, data_size |
确认令牌桶限流 |
send |
queue_size, sent_bytes |
确认实际发送速率 |
2.7.5 对二次开发的启示
- 自定义调度策略:实现加权公平队列(WFQ)
- 动态限速:根据网络状况动态调整令牌生成速率
- 拥塞感知:结合RTT测量实现自适应流控
场景8:Stateless vs Stateful(两种Writer/Reader)
2.8.1 商用需求
| 商用场景 | 需求 | 解决方案 |
|---|---|---|
| 大规模广播 | 一对多、不维护状态 | StatelessWriter |
| 可靠传输 | 维护Reader状态、支持重传 | StatefulWriter |
| 资源受限 | 减少内存占用 | Stateless模式 |
| 高可靠性 | 精确重传、顺序保证 | Stateful模式 |
2.8.2 核心区别
graph LR
subgraph "Stateless模式"
SW[StatelessWriter]
SR[StatelessReader]
SW -->|广播发送| SR
SW -.不维护.-> ReaderState
end
subgraph "Stateful模式"
FW[StatefulWriter]
FR[StatefulReader]
RP[ReaderProxy]
WP[WriterProxy]
FW -->|定向发送| FR
FW -.维护.-> RP
FR -.维护.-> WP
end
2.8.3 源码定位
| 组件 | 源码路径 | 关键类 |
|---|---|---|
| StatelessWriter | src/cpp/rtps/writer/ |
StatelessWriter.cpp |
| StatefulWriter | src/cpp/rtps/writer/ |
StatefulWriter.cpp |
| ReaderProxy | src/cpp/rtps/writer/ |
ReaderProxy.cpp |
| WriterProxy | src/cpp/rtps/reader/ |
WriterProxy.cpp |
2.8.4 gdb调试:两种模式对比
Stateless模式调试:
bash
# 设置断点:Stateless发送
gdb> break eprosima::fastrtps::rtps::StatelessWriter::unsent_change_added_to_history
# 观察:不维护Reader状态,直接广播
Stateful模式调试:
bash
# 设置断点:Stateful发送
gdb> break eprosima::fastrtps::rtps::StatefulWriter::send_to_fixed_locators
# 设置断点:ReaderProxy更新
gdb> break eprosima::fastrtps::rtps::ReaderProxy::acked_changes_set
# 观察:维护每个Reader的状态
对比观察:
| 观察点 | Stateless | Stateful |
|---|---|---|
| Reader状态 | 无 | 有(ReaderProxy) |
| 重传支持 | 不支持 | 支持(基于ACK/NACK) |
| 内存占用 | 低 | 高 |
| 适用场景 | 广播、视频流 | 可靠传输、控制指令 |
2.8.5 对二次开发的启示
- 混合模式:根据Topic选择Stateless或Stateful
- 动态切换:运行时根据网络状况切换模式
- ReaderProxy优化:压缩ReaderProxy状态,减少内存
场景9:Liveliness(活性检测)
2.9.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 节点故障检测 | 检测Publisher是否存活 | Liveliness QoS |
| 心跳超时 | 订阅端感知发布端异常 | LeaseDuration |
| 自动故障转移 | 主备切换 | LivelinessChanged回调 |
| 系统监控 | 监控节点健康状态 | 定期Liveliness检测 |
2.9.2 核心流程
sequenceDiagram
autonumber
participant W as DataWriter
participant LM as LivelinessManager
participant Timer as 定时器
participant R as DataReader
Note over W,R: === 自动模式(AUTOMATIC) ===
W->>LM: 声明Liveliness(写数据时自动)
LM->>Timer: 启动LeaseDuration定时器
loop 定时检查
Timer->>LM: 检查是否超时
alt 未超时
LM->>LM: 重置定时器
else 超时
LM->>R: 通知LivelinessLost
end
end
Note over W,R: === 手动模式(MANUAL_BY_TOPIC) ===
App->>W: 手动调用assert_liveliness
W->>LM: 声明Liveliness
LM->>Timer: 重置定时器
2.9.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| LivelinessManager | src/cpp/rtps/writer/ |
LivelinessManager.cpp |
| 定时器 | src/cpp/rtps/resources/ |
TimedEvent.cpp |
| 回调通知 | src/cpp/rtps/reader/ |
WriterProxy.cpp |
2.9.4 gdb调试:Liveliness检测
bash
# 设置断点:声明活性
gdb> break eprosima::fastrtps::rtps::LivelinessManager::assert_liveliness
# 设置断点:超时检测
gdb> break eprosima::fastrtps::rtps::LivelinessManager::callback
# 设置断点:状态变化通知
gdb> break eprosima::fastdds::dds::DataReaderListener::on_liveliness_changed
# 运行
gdb> run
观察要点:
| 断点位置 | 观察变量 | 商用价值 |
|---|---|---|
assert_liveliness |
guid, lease_duration |
确认活性声明 |
callback |
expired_writers |
确认超时检测 |
on_liveliness_changed |
status.alive_count, status.not_alive_count |
确认状态变化 |
2.9.5 对二次开发的启示
- 自定义检测策略:实现多层级活性检测(应用层 + 传输层)
- 故障预测:基于活性历史数据预测节点故障
- 自动恢复:检测到故障后自动重启或切换
场景10:Persistence(持久化)
2.10.1 商用需求
| 商用场景 | 需求 | Fast-DDS解决方案 |
|---|---|---|
| 系统重启恢复 | 重启后不丢数据 | PersistenceService |
| 历史数据查询 | 查询历史样本 | SQLite持久化 |
| 审计日志 | 数据可追溯 | 持久化存储 + 时间戳 |
| 状态恢复 | 断点续传 | DurabilityService |
2.10.2 核心流程
sequenceDiagram
autonumber
participant DW as DataWriter
participant Cache as WriterHistory
participant PS as PersistenceService
participant DB as SQLite
participant DR as DataReader
Note over DW,DR: === 数据持久化 ===
DW->>Cache: add_change
Cache->>PS: 通知持久化
PS->>DB: INSERT INTO samples
DB->>PS: 确认写入
Note over DW,DR: === 系统重启后恢复 ===
DW->>PS: 启动加载
PS->>DB: SELECT * FROM samples
DB->>PS: 返回历史数据
PS->>Cache: 恢复History
Cache->>DW: 恢复完成
Note over DW,DR: === 新Reader订阅 ===
DR->>DW: 请求历史数据
DW->>Cache: 查询History
Cache->>DR: 发送历史样本
2.10.3 源码定位
| 组件 | 源码路径 | 关键类/函数 |
|---|---|---|
| PersistenceService | src/cpp/rtps/persistence/ |
SQLite3PersistenceService.cpp |
| 持久化Writer | src/cpp/rtps/writer/ |
PersistentWriter.cpp |
| 持久化Reader | src/cpp/rtps/reader/ |
StatefulPersistentReader.cpp |
2.10.4 gdb调试:Persistence流程
bash
# 设置断点:持久化写入
gdb> break eprosima::fastrtps::rtps::SQLite3PersistenceService::add_persistent_sample
# 设置断点:加载历史
gdb> break eprosima::fastrtps::rtps::SQLite3PersistenceService::load_persistent_samples
# 运行
gdb> run
2.10.5 对二次开发的启示
- 自定义存储后端:实现Redis/MySQL持久化
- 数据压缩:持久化前压缩,减少存储空间
- 数据清理策略:实现TTL自动过期
三、调试工具速查表(更新)
3.1 gdb断点速查(完整版)
| 场景 | 关键断点 | 观察目标 |
|---|---|---|
| Discovery | PDP::announce_participant_state EDP::pairing_writer_reader |
发现流程、匹配逻辑 |
| SHM零拷贝 | SharedMemTransport::send SharedMemTransport::receive |
零拷贝路径、缓冲区管理 |
| DataSharing | WriterPool::create_new_payload DataSharingNotification::notify |
完全零拷贝、通知机制 |
| Reliable传输 | StatefulWriter::send_heartbeat StatefulWriter::process_acknack |
心跳、ACK、重传 |
| 分片传输 | RTPSMessageGroup::add_data_frag FragmentedChangePitStop::process |
分片生成、重组 |
| FlowControl | FlowController::schedule ThroughputController::process |
流量控制、令牌桶 |
| Stateless/Stateful | StatelessWriter::unsent_change_added StatefulWriter::send_to_fixed_locators |
两种模式差异 |
| Liveliness | LivelinessManager::assert_liveliness LivelinessManager::callback |
活性检测、超时 |
| Persistence | SQLite3PersistenceService::add_persistent_sample |
持久化写入 |
| QoS策略 | WriterHistory::add_change WriterHistory::remove_min_change |
History管理、资源限制 |
| 数据流 | DataWriter::write DataReader::take_next_sample |
端到端数据流 |
四、推荐材料学习
| 类型 | 资源 | 说明 |
|---|---|---|
| 官方文档 | Fast-DDS RTPS层 | fast-dds.docs.eprosima.com/en/latest/f... |
| 官方文档 | Fast-DDS Discovery | fast-dds.docs.eprosima.com/en/latest/f... |
| 官方文档 | Fast-DDS SHM | fast-dds.docs.eprosima.com/en/latest/f... |
| 论文 | RTPS Spec 8.5 | OMG官方文档 - Discovery |
| 论文 | RTPS Spec 8.6 | OMG官方文档 - Reliable Protocol |
| 工具 | GDB手册 | sourceware.org/gdb/current... |
| 工具 | Wireshark RTPS | wiki.wireshark.org/RTPS |
六、数据源标注
6.1 核心源码路径
| 模块 | 路径 | 关键文件 |
|---|---|---|
| Discovery | src/cpp/rtps/builtin/discovery/participant/ |
PDP.cpp, PDPListener.cpp |
| Discovery | src/cpp/rtps/builtin/discovery/endpoint/ |
EDP.cpp, EDPSimple.cpp |
| SHM Transport | src/cpp/rtps/transport/shared_mem/ |
SharedMemTransport.cpp, SharedMemManager.cpp |
| Reliable Writer | src/cpp/rtps/writer/ |
StatefulWriter.cpp |
| Reliable Reader | src/cpp/rtps/reader/ |
StatefulReader.cpp |
| 分片 | src/cpp/rtps/messages/ |
RTPSMessageGroup.cpp |
| 分片重组 | src/cpp/rtps/reader/ |
FragmentedChangePitStop.cpp |
| History | src/cpp/rtps/history/ |
WriterHistory.cpp, ReaderHistory.cpp |
| QoS | include/fastdds/dds/core/policy/ |
QosPolicies.hpp |
| Topic | src/cpp/fastdds/topic/ |
Topic.cpp, TopicImpl.cpp |
6.2 示例代码
| 示例 | 路径 | 说明 |
|---|---|---|
| hello_world | examples/cpp/hello_world/ |
基础发布订阅 |
| discovery_server | examples/cpp/discovery_server/ |
发现服务器模式 |
| delivery_mechanisms | examples/cpp/delivery_mechanisms/ |
传输机制对比 |
| flow_control | examples/cpp/flow_control/ |
流量控制QoS |
| custom_payload_pool | examples/cpp/custom_payload_pool/ |
自定义内存池 |
七、学习检查清单与解答
| 检查项 | 解答要点 |
|---|---|
| Discovery如何满足大规模集群? | 使用Discovery Server模式,减少广播风暴 |
| SHM如何实现零拷贝? | 通过mmap共享内存,用户态直接读写,无内核拷贝 |
| Reliable传输如何保证送达? | Heartbeat + ACK/NACK机制,超时重传 |
| 分片传输何时触发? | 数据大小超过Transport的max_message_size |
| gdb如何观察Discovery? | 在PDP::announce_participant_state和EDP::pairing_writer_reader设置断点 |
| strace如何验证SHM零拷贝? | 观察只有mmap操作,没有sendto/recvfrom |
| Wireshark如何分析Reliable? | 过滤rtps.sm.id == 0x07(Heartbeat)和rtps.sm.id == 0x06(ACK) |
| QoS策略在哪些阶段生效? | write → History → send → receive → read,每个阶段都有QoS检查 |
本章内容按五件套规范编写,分场景、分模块组织,每个场景包含:商用需求、核心流程、源码定位、gdb调试、二次开发启示。