Fast DDS核心三要素:发现、传输与QoS深度解析 📅 发布时间:2026/9/12 20:10:11 👁 浏览次数: 1. Fast DDS 到底怎么用先搞清它不是“另一个ROS2通信层”Fast DDS 是 eProsima 开发的开源实现严格遵循 OMG对象管理组织发布的 DDSData Distribution Service标准。很多人第一次接触它是通过 ROS2——因为 ROS2 默认底层通信中间件就是 Fast DDS。但这里必须划重点Fast DDS 本身是一个独立、完整、可脱离 ROS2 单独部署的实时发布-订阅中间件。它不依赖 ROS2也不等同于 ROS2 的“网络模块”。把它当成“ROS2 的网卡驱动”是常见误解更准确地说它是 ROS2 可插拔的“通信引擎之一”就像汽车可以换装不同品牌的发动机。标题里问“到底怎么用”核心痛点其实很现实刚跑通一个 HelloWorld 示例一加 QoS 策略就收不到数据开了两个节点互相“看不见”日志里反复刷discovery failed想传个 10MB 的图像结构体吞吐直接掉到 2fpsCPU 却只占 15%……这些不是配置写错了而是对 Fast DDS 的三大支柱——发现Discovery、传输Transport与服务质量QoS——缺乏系统性理解导致参数之间相互打架。比如“电脑打开了网络发现为什么别的用户看不着”这个热词表面是 Windows 网络设置问题但内核逻辑和 Fast DDS 的发现机制高度同源都是靠周期性广播/组播“我是谁、我在哪、我能提供什么服务”来建立连接。只不过 Windows 用的是 SSDP 或 NetBIOS而 Fast DDS 用的是 RTPSReal-Time Publish-Subscribe协议定义的 Participant Discovery 和 Endpoint Discovery。你配错一个builtin发现模式或者防火墙拦了 7400 端口效果就是“节点在线但彼此失联”和“局域网可见性异常”本质一致。再比如“TLS 是安全传输层协议用于在两个通信应用程序之间提供保密性和数据完整性”这句描述放在 HTTPS 场景完全正确但套到 Fast DDS 上就容易踩坑。Fast DDS 支持 TLS但它不是简单地给 TCP 连接套一层壳它要求证书链、密钥格式、验证策略、甚至证书有效期都必须严格匹配 RTPS 的安全模型DDS Security spec。我曾见过团队把 Nginx 的 PEM 证书直接丢进 Fast DDS 配置结果握手失败日志只报security validation failed查了三天才发现是证书缺少 Subject Alternative NameSAN字段——这种细节官方文档不会手把手教但实操中天天遇到。所以这篇文章不讲“如何安装 Fast DDS”也不堆砌 API 列表。我要带你像调试一台精密仪器一样一层层拧开它的外壳从最底层的网络包怎么发、被谁收到传输到节点怎么“认出彼此”发现再到当带宽不足、延迟敏感、数据关键时你该动哪个旋钮、调哪组参数QoS。所有内容基于 v2.14.x当前 LTS 版本实测配置项全部附带真实场景下的取值依据不是“建议设为 true”而是“为什么此处必须设为 false”。2. 发现机制不是“自动连上”而是“按规则找到对方”2.1 发现的本质三类消息 两种模式Fast DDS 的发现过程本质是三个角色Participant、Publisher、Subscriber之间交换四类 RTPS 消息HEARTBEAT,ACKNACK,DATA,GAP。但真正决定“能不能看见对方”的是前两类控制消息Participant Discovery参与者发现和Endpoint Discovery端点发现。前者解决“你是谁”后者解决“你能发什么、收什么”。发现不是魔法它严格依赖两种模式的组合Simple Discovery默认轻量级适用于单播或小规模组播网络。它把 Participant 和 Endpoint 信息打包进一个DATA消息通过预设的“初始 Peer List”Initial Peers List发送。这个列表就像通讯录你得手动告诉本节点“隔壁那台机器 IP 是 192.168.1.102端口是 7400”它才会主动去“敲门”。Static Discovery静态发现零广播、零组播完全靠配置文件驱动。所有 Participant 和 Endpoint 的元数据GUID、类型名、QoS 策略哈希值都写死在 XML 文件里。启动时每个节点加载自己的 XML然后按文件里写的地址和 GUID直接向目标节点发起单播连接。适合高安全、强管控环境比如军工嵌入式设备禁止任何动态广播行为。提示很多初学者以为“开了发现就自动互联”结果在 Docker 容器或 Kubernetes Pod 里死活连不上。根本原因是 Simple Discovery 默认用组播239.255.0.1:7400而容器网络通常禁用组播。此时必须显式配置 Initial Peers List把其他节点的 IP:Port 写进去强制走单播。2.2 关键配置项详解从builtin到initialPeersList发现行为由DomainParticipantQos中的builtin子模块控制。核心参数如下参数路径类型默认值实际含义常见误用builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocolbooltrue是否启用 Simple Endpoint 发现设为 false 后未配 Static导致完全无法发现 Topicbuiltin.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriterbooltruePublisher 节点是否广播自己能发什么 Topic关闭后 Subscriber 找不到 Publisher但日志无明确提示builtin.initial_peers_listlist of string[239.255.0.1:7400]初始 Peer 地址列表格式IP:PORT写成192.168.1.102缺端口或localhost:7400Docker 内无效举个真实案例某 AGV 调度系统有 5 台工控机每台运行一个 Fast DDS Participant。网络是千兆交换机直连无 VLAN。最初用默认组播测试时一切正常上线后某天突然 2 台机器失联。抓包发现交换机某端口因流量突增触发了 IGMP Snooping 限速组播包被丢弃。解决方案不是换协议而是将initial_peers_list显式设为所有 5 台机器的 IP:7400强制走单播。这样既绕过组播依赖又保持了 Simple Discovery 的灵活性。注意initial_peers_list中的地址必须是目标节点实际监听的地址。Fast DDS 默认监听0.0.0.0所有接口但如果你在TransportDescriptor里绑定了特定网卡如enp0s31f6那么initial_peers_list就必须填该网卡的 IP而不是127.0.0.1或localhost。我踩过的坑在双网卡笔记本上开发initial_peers_list写127.0.0.1结果另一台机器连过来数据全发到回环口本地收不到。2.3 发现失败排查三步定位法当ros2 node list看不到节点或自定义程序wait_for_subscriptions()卡住按此顺序排查确认网络连通性# 检查目标节点 7400 端口是否开放注意Fast DDS 默认用 UDP nc -zuv 192.168.1.102 7400 # 或更直接用 tcpdump 抓 RTPS 包 sudo tcpdump -i any -n port 7400如果nc不通说明防火墙或网络策略拦截如果tcpdump完全没包说明本节点根本没发发现请求——检查initial_peers_list是否为空或格式错误。检查 Participant GUID 是否冲突Fast DDS 要求每个 Participant 有唯一 GUID。若多个进程用相同 XML 配置启动尤其在 Docker 中未指定domain_idGUID 可能重复导致发现消息被静默丢弃。验证方法启动时加-v参数看日志中GUIDPREFIX是否各不相同。验证 Topic 名称与类型是否严格一致Publisher和Subscriber的 Topic 名称如/sensor/camera、数据类型如sensor_msgs::msg::Image_必须字节级完全相同。ROS2 中常因.idl文件生成路径不同、CMakeLists.txt 中rosidl_generate_interfaces()调用顺序差异导致同一类型在不同节点编译出不同哈希值。此时发现成功Participant 可见但 Endpoint 发现失败日志会显示Type not matched。解决方案统一使用rosidl_typesupport_introspection_cpp并在 CMake 中强制指定类型支持包。3. 传输层UDP 是默认但不是唯一更不是最优3.1 传输的本质不只是“发包”而是“选路保活适配”Fast DDS 的传输层Transport Layer负责把序列化后的数据从发送方内存可靠/不可靠地送达接收方内存。它不关心数据是什么那是序列化层的事只关心“怎么送、送几次、超时多久、走哪条路”。默认使用UDPv4TransportDescriptor但这只是起点。真正的传输能力取决于你如何组合以下三类传输描述符基础传输Base TransportUDPv4,UDPv6,TCPv4,TCPv6,SHMShared Memory本机进程间最快。安全传输Secure TransportTLS基于 OpenSSL、DTLSUDP 上的 TLS。自定义传输Custom Transport通过继承TransportInterface实现私有协议如对接 CAN FD、TSN时间敏感网络。很多人以为“UDP 快所以选 UDP”这是片面的。UDP 确实无连接、低开销但它的“快”是有前提的网络质量好、丢包率 0.1%、单跳延迟 1ms。一旦跨交换机、经无线 AP、或在车载 ECU 的 CAN 网关后UDP 丢包率可能飙升至 5%此时重传机制缺失数据就真丢了。而 TCP 虽有连接建立、拥塞控制开销但在高丢包环境下其 ARQ自动重传请求机制反而保障了最终送达。3.2 UDP 传输深度调优从sendBufferSize到maxMessageSizeUDP 传输看似简单但几个关键参数直接影响吞吐与稳定性sendBufferSize/receiveBufferSize操作系统 socket 缓冲区大小单位字节。默认 64KB但千兆网卡满速传输时建议设为2 * 1024 * 10242MB。原因Linux 内核中UDP 缓冲区过小会导致send()系统调用阻塞即使应用层认为“发出去了”实际数据卡在内核队列。实测某雷达点云流单帧 8MBsendBufferSize64KB时吞吐仅 120MB/s设为 2MB 后稳定达 940MB/s接近千兆线速。maxMessageSize单个 RTPS 消息最大尺寸单位字节。默认 65500但受 MTUMaximum Transmission Unit限制。以太网标准 MTU 是 1500 字节减去 IP 头20B和 UDP 头8B实际有效载荷约 1472B。Fast DDS 为避免分片会将大消息拆成多个 Fragment。maxMessageSize设得太小如 1000会导致一个 1MB 图像被切成 1000 个 Fragment每个 Fragment 都要带 RTPS Header至少 24B头部开销暴涨 2.4%。我们推荐设为65000接近理论最大值并确保网络路径支持 Jumbo FrameMTU9000。ttlTime-To-LiveIP 包生存时间。默认 1即只允许在同一子网内传输。若需跨路由器如办公网到产线网必须设为1如 64。但注意ttl64并不意味能跨 64 跳而是每经过一个路由器值减 1为 0 时丢包。生产环境建议设为32平衡可达性与安全性。!-- 示例高性能 UDP 传输配置 -- transport_descriptors transport_descriptor transport_idudp_high_perf/transport_id typeUDPv4/type send_socket_buffer_size2097152/send_socket_buffer_size receive_socket_buffer_size2097152/receive_socket_buffer_size max_message_size65000/max_message_size ttl32/ttl /transport_descriptor /transport_descriptors3.3 TCP 与 SHM何时该放弃 UDPTCP 适用场景跨广域网WAN通信如总部与工厂远程监控对可靠性要求极高且能接受 50~200ms 级延迟如 PLC 控制指令下发网络存在 NAT 或防火墙UDP 端口难穿透TCP 可走 443 端口伪装 HTTPS。配置要点关闭 Nagle 算法nagle_disabledtrue避免小包合并引入延迟增大keep_alive_interval如 30s防止空闲连接被中间设备断开。SHM共享内存适用场景同一物理机上的多进程通信如 ROS2 中rviz2与robot_state_publisher对延迟极度敏感 10μs如实时运动控制闭环。配置要点shared_mem_transport必须在DomainParticipantQos中显式启用segment_size共享内存段大小建议设为100 * 1024 * 1024100MB避免频繁重分配。实操心得某客户做无人机集群仿真100 个 UAV 节点全跑在同一台服务器。最初用 UDPCPU 占用 85%延迟抖动大。切换为 SHM 后CPU 降至 22%平均延迟从 180μs 降到 3.2μs。关键不是“换协议”而是识别出通信发生在同一物理内存空间SHM 是唯一合理选择。盲目追求“通用性”而不用 SHM是性能浪费。4. QoS 配置不是调参而是为数据“买保险”4.1 QoS 的本质一份数据传输的“服务契约”QoSQuality of Service不是性能开关而是 Publisher 和 Subscriber 之间协商的一份“服务契约”。它定义了数据是否必须送达Reliability、能容忍多长延迟Deadline、是否允许旧数据覆盖新数据Liveliness、以及当网络拥塞时谁的数据该被丢弃Destination Order。所有 QoS 策略必须成对配置Publisher 设RELIABLESubscriber 也必须设RELIABLE否则发现阶段就会拒绝匹配。Fast DDS 定义了 23 种 QoS 策略但日常开发中90% 的问题集中在以下 5 种QoS 策略作用域关键取值典型场景风险提示ReliabilityPublisher/SubscriberBEST_EFFORT,RELIABLEBEST_EFFORT: 视频流、传感器原始数据RELIABLE: 控制指令、状态上报RELIABLE会开启重传增加内存与 CPU 开销BEST_EFFORT下丢包无通知DurabilityPublisher/SubscriberVOLATILE,TRANSIENT_LOCAL,TRANSIENT,PERSISTENTTRANSIENT_LOCAL: 本地历史数据缓存如机器人地图VOLATILE: 实时流数据TRANSIENT要求外部持久化服务配置复杂新手慎用HistoryPublisher/SubscriberKEEP_LAST,KEEP_ALLdepthKEEP_LASTwithdepth10: 缓存最近 10 条消息KEEP_ALL: 内存无限增长KEEP_ALL在高频 Topic如 IMU 1kHz下几秒就 OOMResourceLimitsPublisher/Subscribermax_samples,max_instances,max_samples_per_instancemax_samples1000: 全局最多存 1000 条max_instances100: 最多支持 100 个不同 ID 的实例必须与History配合否则KEEP_LAST无意义DeadlinePublisher/Subscriberperiod(e.g.,100 ms)period100ms: 要求数据在 100ms 内送达超时触发on_offered_incompatible_qos回调Deadline本身不保证时效只提供超时通知需业务层处理4.2 Reliability 与 History 的黄金组合解决“为什么收不到最新数据”这是最高频问题“我发了 100 条Subscriber 只收到第 1 条和第 100 条”。根源在于Reliability和History的错配。ReliabilityRELIABLEHistoryKEEP_LAST, depth1Publisher 会确保每条消息送达但只保留最新 1 条。若 Subscriber 启动晚了它只能收到最后那条前面 99 条永远丢失。这是“保送达不保历史”。ReliabilityRELIABLEHistoryKEEP_LAST, depth100Publisher 缓存最近 100 条Subscriber 启动后会收到这 100 条按序。这是“保送达 保近期历史”。ReliabilityBEST_EFFORTHistoryKEEP_LAST, depth100Publisher 不保证送达但缓存 100 条。Subscriber 可能收到乱序、重复、缺失的数据。这是“尽力而为 有缓冲”。实操技巧某激光 SLAM 系统/scanTopic 频率 10Hz。我们配置ReliabilityRELIABLEHistoryKEEP_LAST, depth5。理由SLAM 算法只需最近 500ms 的扫描数据5 条depth5刚好覆盖内存占用可控RELIABLE保证关键帧不丢避免建图断裂。若设depth100内存多占 20MB毫无必要。4.3 Deadline 与 Liveliness让系统“自我诊断”Deadline和Liveliness是系统健康度的“心跳监测器”。DeadlinePublisher 承诺“每 X 时间发一次数据”。Subscriber 监测到连续 N 次未收到触发回调。这不是延迟报警而是数据流中断预警。例如IMU 驱动应每 1ms 发一次设period2ms若 2ms 内没收到说明驱动卡死或硬件故障。LivelinessPublisher 承诺“我活着且能发数据”。它会周期性发HEARTBEAT。Subscriber 监测到lease_duration租约时长内无心跳触发on_liveliness_changed。这比Deadline更底层检测的是 Publisher 进程是否崩溃。配置示例// Publisher QoS publisher_qos.endpoint().history_kind eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS; publisher_qos.endpoint().history_depth 10; publisher_qos.endpoint().reliability().kind eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; publisher_qos.endpoint().deadline().period eprosima::fastrtps::Duration_t(0, 2000000); // 2ms publisher_qos.endpoint().liveliness().kind eprosima::fastdds::dds::AUTOMATIC_LIVELINESS_QOS; publisher_qos.endpoint().liveliness().lease_duration eprosima::fastrtps::Duration_t(1, 0); // 1s注意Deadline.period和Liveliness.lease_duration的单位是秒.纳秒Duration_t(sec, nanosec)。0, 2000000表示 2ms不是0.002。写错单位是新手最常犯的错误导致 deadline 永远不触发。5. 实战配置全流程从零开始搭建一个可靠图像传输系统5.1 需求分析明确“可靠”的定义假设我们要构建一个工业相机图像采集系统相机GigE Vision输出1920x108030fps的RGB8图像传输千兆以太网单跳交换机无无线段可靠性要求允许单帧丢失但不允许花屏、撕裂延迟 ≤ 100msCPU 占用 40%部署一台工控机Camera Node采集一台服务器Viewer Node显示与存储。这意味着Reliability选BEST_EFFORT视频流天然容错History选KEEP_LAST, depth3缓存 3 帧防短暂抖动Transport必须用UDPv4且sendBufferSize调大Deadline设为33ms30fps 周期超时即告警ResourceLimits必须限制防内存爆炸。5.2 XML 配置文件编写可复用的模板创建image_transport_profile.xml?xml version1.0 encodingUTF-8? profiles xmlnshttp://www.eprosima.com/XMLSchemas/fastRTPSProfile transport_descriptors transport_descriptor transport_idudp_image/transport_id typeUDPv4/type send_socket_buffer_size4194304/send_socket_buffer_size !-- 4MB -- receive_socket_buffer_size4194304/receive_socket_buffer_size max_message_size65000/max_message_size ttl1/ttl /transport_descriptor /transport_descriptors participant profile_nameimage_participant is_default_profiletrue rtps nameImageTransportParticipant/name builtin discovery_config use_SIMPLE_EndpointDiscoveryProtocoltrue/use_SIMPLE_EndpointDiscoveryProtocol m_simpleEDP use_PublicationReaderANDSubscriptionWritertrue/use_PublicationReaderANDSubscriptionWriter /m_simpleEDP initialPeersList locator udpv4 address192.168.1.101/address !-- Viewer IP -- port7400/port /udpv4 /locator locator udpv4 address192.168.1.102/address !-- Camera IP -- port7400/port /udpv4 /locator /initialPeersList /discovery_config /builtin userTransports transport_idudp_image/transport_id /userTransports useBuiltinTransportsfalse/useBuiltinTransports /rtps /participant publisher profile_nameimage_publisher qos endpoint history_kindKEEP_LAST_HISTORY_QOS/history_kind history_depth3/history_depth reliability_kindBEST_EFFORT_RELIABILITY_QOS/reliability_kind resource_limits max_samples1000/max_samples max_instances1/max_instances max_samples_per_instance1000/max_samples_per_instance /resource_limits deadline period sec0/sec nanosec33000000/nanosec !-- 33ms -- /period /deadline /endpoint /qos /publisher subscriber profile_nameimage_subscriber qos endpoint history_kindKEEP_LAST_HISTORY_QOS/history_kind history_depth3/history_depth reliability_kindBEST_EFFORT_RELIABILITY_QOS/reliability_kind resource_limits max_samples1000/max_samples max_instances1/max_instances max_samples_per_instance1000/max_samples_per_instance /resource_limits deadline period sec0/sec nanosec33000000/nanosec /period /deadline /endpoint /qos /subscriber /profiles5.3 C 代码集成加载配置并创建实体#include fastdds/dds/domain/DomainParticipantFactory.hpp #include fastdds/dds/publisher/Publisher.hpp #include fastdds/dds/subscriber/Subscriber.hpp #include fastdds/dds/domain/DomainParticipant.hpp #include fastdds/dds/publisher/DataWriter.hpp #include fastdds/dds/subscriber/DataReader.hpp #include fastdds/dds/topic/Topic.hpp #include fastdds/dds/topic/TypeSupport.hpp #include fastdds/rtps/attributes/PropertyPolicy.h #include fastdds/dds/log/Log.hpp // 假设已定义 ImageTypeSupportIDL 生成 using namespace eprosima::fastdds::dds; int main(int argc, char** argv) { // 1. 禁用默认日志避免干扰 eprosima::fastdds::dds::Log::SetVerbosity(eprosima::fastdds::dds::Log::Warning); // 2. 创建 DomainParticipant加载 XML 配置 DomainParticipantQos pqos; DomainParticipantFactory::get_instance()-load_profiles(); DomainParticipantFactory::get_instance()-get_default_participant_qos(pqos); // 加载名为 image_participant 的 profile DomainParticipantFactory::get_instance()-get_participant_qos_from_profile( image_participant, pqos); DomainParticipant* participant DomainParticipantFactory::get_instance()- create_participant(0, pqos); if (!participant) { std::cerr Failed to create participant std::endl; return -1; } // 3. 注册类型 ImageTypeSupport type_support; type_support.register_type(participant); // 4. 创建 Topic Topic* topic participant-create_topic( camera/image_raw, Image, TOPIC_QOS_DEFAULT); if (!topic) { std::cerr Failed to create topic std::endl; return -1; } // 5. 创建 Publisher使用 image_publisher profile PublisherQos pub_qos; DomainParticipantFactory::get_instance()-get_publisher_qos_from_profile( image_publisher, pub_qos); Publisher* publisher participant-create_publisher(pub_qos); // 6. 创建 DataWriter DataWriterQos dw_qos; DomainParticipantFactory::get_instance()-get_datawriter_qos_from_profile( image_publisher, dw_qos); DataWriter* writer publisher-create_datawriter(topic, dw_qos); // 7. 创建 Subscriber使用 image_subscriber profile SubscriberQos sub_qos; DomainParticipantFactory::get_instance()-get_subscriber_qos_from_profile( image_subscriber, sub_qos); Subscriber* subscriber participant-create_subscriber(sub_qos); // 8. 创建 DataReader DataReaderQos dr_qos; DomainParticipantFactory::get_instance()-get_datareader_qos_from_profile( image_subscriber, dr_qos); DataReader* reader subscriber-create_datareader(topic, dr_qos); // 9. 启动传输此处省略具体 send/receive 循环 // ... // 10. 清理 participant-delete_contained_entities(); DomainParticipantFactory::get_instance()-delete_participant(participant); return 0; }5.4 性能压测与调优用真实数据说话编译后用stress-ng模拟 CPU 负载iperf3占用网络带宽进行三轮测试测试场景sendBufferSizemaxMessageSizehistory_depth平均延迟CPU 占用是否出现花屏默认配置64KB65500142ms68%是偶发优化配置4MB65000328ms32%否极致配置8MB65000522ms38%否但内存多占 120MB结论4MB 缓冲 depth3 是最佳平衡点。再往上延迟收益递减内存压力陡增。这印证了开头的观点QoS 不是调参而是根据物理约束网卡、内存、CPU做的工程权衡。6. 常见问题与独家避坑指南6.1 “QoS 不兼容”错误23 种策略的匹配逻辑错误日志WARNING: Incompatible QoS policies detected。这不是配置错误而是 Publisher 和 Subscriber 的 QoS 策略存在强制不兼容项。Fast DDS 将 23 种策略分为三类强制兼容MandatoryReliability,Durability,History,ResourceLimits。只要有一项不匹配发现失败节点不可见。可选兼容OptionalDeadline,Liveliness,Ownership。不匹配会触发警告但连接仍建立数据可收发。忽略兼容IgnoredUser Data,Topic Data,Group Data。完全不影响发现。排查步骤用dds::core::policy::CompatibilityQosPolicy获取不兼容详情重点检查Reliability和History是否完全一致包括depth值若用 ROS2检查rmw_fastrtps_cpp的rmw_qos_profile_sensor_data等预设 Profile它们对History.depth有默认值通常是 1而你的自定义 Publisher 可能设为 10导致不匹配。6.2 “内存泄漏”幻觉Fragment 缓存未释放现象程序运行数小时后RSS 内存持续上涨valgrind无泄漏报告。根源是RELIABLE模式下Publisher 为每个 Subscriber 维护一个Fragment缓存队列用于重传。若 Subscriber 频繁启停如调试时 CtrlCPublisher 不会立即清理对应队列直到heartbeat_response_delay超时默认 10s。解决方案在 Subscriber 正常退出前调用subscriber-delete_contained_entities()或在 Publisher QoS 中缩短reliability().max_blocking_time如设为0, 100000000即 100ms加速超时清理。6.3 Docker 部署网络模式与端口映射陷阱在 Docker 中运行 Fast DDS必须避开两个坑网络模式--networkhost最简单但丧失隔离性--networkbridge需手动映射7400端口且initial_peers_list必须填宿主机 IP非172.17.0.2UDP 分片Docker bridge 网络的 MTU 通常是 1500但容器内应用看到的 MTU 可能是 65535虚拟设备。这导致maxMessageSize65000的包在容器内被内核分片而 Fast DDS 的 Fragment 重组逻辑无法处理 IP 层分片。解决方案启动容器时加--mtu1500或在transport_descriptor中显式设maxMessageSize1400。6.4 跨平台调试Windows 与 Linux 的行为差异WindowssendBufferSize设置上限受netsh int ipv4 set dynamicport影响默认动态端口范围小可能导致bind()失败。需扩大范围netsh int ipv4 set dynamic