ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

自动驾驶通信中间件:DDS与SOME/IP的区别与协同选型

自动驾驶通信中间件:DDS与SOME/IP的区别与协同选型 先说个结论在自动驾驶域控制器的软件架构里DDS和SOME/IP这两个词几乎是绕不开的。做感知融合的同事天天跟DDS的QoS策略搏斗做整车服务设计的又在反复调SOME/IP的服务发现周期。很多新人刚入行时都会问这两个协议到底什么关系是不是选一个就行答案其实没那么简单。这篇文章就围绕自动驾驶场景把这两个协议的核心机制、主要差异、选型思路和协同方案摊开讲一遍顺便把我踩过的坑也列出来。先给还没接触过这块的读者交个底DDS是Data Distribution Service面向数据分发的标准SOME/IP是Scalable service-Oriented MiddlewarE over IP面向服务调用的中间件。两者都是跑在以太网上的通信中间件但设计出发点完全不同。理解了它们各自擅长什么再回头看自动驾驶里的部署思路就清楚很多。1. 为什么自动驾驶通信层会同时出现DDS和SOME/IP1.1 从信号总线到服务化通信的演变传统整车通信主要靠CAN和LIN信号和报文在开发阶段就把矩阵定义死了。CAN一个PUD里塞一堆信号的“信号打包”模式在ADAS和自动驾驶出现之前是够用的因为传感器数量少、功能逻辑相对固定。但到了自动驾驶阶段摄像头、激光雷达、毫米波雷达、IMU加上高精地图和车路协同数据量从每秒几百字节飙到每秒几GB节点数量也从几十个变成上百个动态上下线、按需订阅、跨域融合成了常态。这时候如果继续用静态矩阵的思路设计通信开发效率会很差每加一个传感器都要重新分配报文ID和信号位还要改所有人对表。而且传统总线不太支持“发布订阅”这种一对多、按需通信的模式。于是行业转向以太网技术开始把通信协议“软件化、服务化”。DDS和SOME/IP都是在这个背景下被拿出来讨论的技术方案。它们解决的问题相似但切入点完全不同。DDS从“数据为中心”出发强调数据的分发、可靠性和实时性SOME/IP从“服务为中心”出发把软件功能抽象成服务接口通过服务的注册、发现和调用来组织通信。一个偏数据流一个偏服务调用天然就有互补性。1.2 DDS和SOME/IP各自从哪里来DDS由对象管理组织OMG制定标准最早用在航空、航天、国防等对实时性要求极高的分布式系统里。1990年代末到2000年代初OMG在实时数据分发规范上持续演进定义了数据模型、QoS策略和RTPS线协议。RTPS基于UDP/IP支持组播和单播节点之间可以动态发现。ROS 2的底层通信就选用了DDS这让DDS在机器人领域的知名度迅速放大。SOME/IP则来自AUTOSAR体系AUTOSAR CP R4.0之后开始大规模推广。它可以跑在AUTOSAR CP的经典平台上也可以跑在APAdaptive Platform上。它本质上是一个“面向服务的RPC消息通知”协议把ECU能力封装成服务通过SOME/IP-SD来做服务发现通过Request/Response和Event/Field来通信。SOME/IP更贴合整车EEA架构中“SOA化”的趋势。这里必须提醒一个容易混淆的地方DDS这三个字母在不同领域含义不同。在自动驾驶和机器人领域DDS是Data Distribution Service在FPGA和射频领域DDS是Direct Digital Synthesis直接数字频率合成是硬件芯片或IP核的一种功能。搜索引擎搜“DDS芯片”“DDS IP核”会出来一堆做信号发生器的东西跟通信协议没半毛钱关系。做自动驾驶的工程师看资料时一定要先分清语境不然很容易跑偏。1.3 自动驾驶为什么需要两套协议并行自动驾驶车辆的软件架构里既存在大量高频、大带宽的传感器数据流也存在很多偶发、低带宽但要求可靠的服务调用。前者的关键词是“流”比如点云流、图像流、融合目标流后者的关键词是“事”比如打开一个诊断服务、读取一个配置、下发一个控制指令。DDS对“流”的支持非常成熟它支持实时发布订阅QoS可以配置成Best Effort或者Reliable还有Deadline、Liveliness等策略保障数据的新鲜度和节点活跃度加上Partition和Domain的隔离机制天然适合感知融合、规划轨迹这类需要不断刷新、多节点同时订阅的数据。SOME/IP对“事”的支持则更轻量一个Method调用对应一个请求和响应一个Event对应一类状态变化服务发现机制让ECU可以在运行时感知服务是否可用。所以很多量产方案不是二选一而是让DDS承担域内大数据分发SOME/IP承担跨域服务调用和整车SOA接入。两者通过网关或中间层桥接各自发挥优势。这也是后面协同架构的基础认知。2. DDS与SOME/IP核心机制拆解2.1 DDS的数据分发模型与QoS策略DDS的核心模型是“全局数据空间”。每个节点创建DomainParticipant加入同一个Domain后就能通过Topic名字发布和订阅数据。发布端叫DataWriter订阅端叫DataReader两端可以在运行期自动发现不需要中心服务器。RTPS协议是DDS的底层通信协议默认跑在UDP上。同一网络内的Participant通过SPDPSimple Participant Discovery Protocol互相发现再通过SEDPSimple Endpoint Discovery Protocol交换Writer和Reader的信息。发现完成后数据直接走UDP单播或组播。跨网段部署则需要路由器支持组播或者开启IGNORE_LOCAL_ENDPOINTS、使用RTPS discovery server等机制。DDS最核心的竞争力是QoS策略。常用的几个RELIABILITYRELIABLE可靠还是BEST_EFFORT尽力而为。激光雷达点云、摄像头图像这种海量数据通常用Best Effort降低开销控制指令和状态同步用Reliable保证不丢。DURABILITY控制历史数据的持久性。可以配置到一个Publisher刚创建时就把最近数据推给新加入的Subscriber。DEADLINE规定数据更新的最大时间间隔超过就触发回调适合监测信号超时。LIVELINESS检测和宣告节点是否还活着一般靠心跳包实现。PARTITION逻辑隔离不同Partition内的Writer和Reader互相看不到适合在同一Domain里划分功能域。DDS还有一个容易被忽略但实际很有用的特性是Partition。它和Domain不一样Domain是基于网络和参与者的硬隔离不同Domain之间完全不可见Partition是基于逻辑名字的软隔离同一个Domain里的多个Partition各自独立。实际工程中可以用Domain区分不同的ECU或通信区域用Partition区分同一功能内部的采集数据、结果数据、调试数据类似“同一个办公室里的不同部门”。2.2 SOME/IP的服务发现与调用模型SOME/IP是面向服务的服务接口有三种基本元素Method方法、Event事件、Field属性。Method是经典的请求/响应调用比如“读取BMS的SOC”Event是服务方主动推送状态变化比如“车门状态变化”Field集合了两者可以读、可以写、也可以被订阅。SOME/IP-SDService Discovery负责服务的发现与状态管理。服务提供方启动后会发送OfferService报文声明自己能提供哪些服务、服务实例ID、服务的Endpoint地址和端口服务消费者收到后可以发送SubscribeEventgroup订阅事件或者直接发Request调用Method。SD报文会周期性或按事件重发用来维持服务状态。实际部署时启动阶段SD报文会比较密集之后会逐步进入稳态。SOME/IP的头部协议和序列化非常紧凑面向AUTOSAR的绑定也做得很好。它的序列化规则支持基础类型、数组、结构体、可选字段、字符串等也有针对AUTOSAR的严格对齐规则。消息默认走UDP超过MTU的大消息会走TCP或者在UDP上做分片。每个服务实例的消息ID需要在整车系统设计时统一定义和CAN报文矩阵类似但比CAN灵活得多。2.3 一张表看透核心差异维度DDSSOME/IP标准化组织OMGAUTOSAR通信范式发布/订阅服务调用RPC/Event/Field核心抽象Topic Writer/ReaderService Method/Event发现机制RTPS自动发现SPDP/SEDPSOME/IP-SD周期与事件发现QoS能力非常丰富可靠性、时限、存活等相对固定主要靠传输层保障传输层主要为UDP支持共享内存/多播等UDP/TCPTCP用于大消息实时性可配置支持确定性QoS依赖调度和网络需自行控制部署复杂度较高QoS配置需要经验较低接口定义规范典型场景传感器数据分发、域内融合、机器人诊断服务、整车服务化、跨域调用这张表不是用来判断谁好谁坏而是用来指路。如果做一个传感器融合的“数据分发总线”DDS是天生适合的如果做一个HMI开关控制或者诊断服务SOME/IP会顺手很多。二者最大的区别不是性能而是抽象模型DDS面向“数据”SOME/IP面向“服务”。2.4 两种协议的实际落地生态DDS在开源社区有很多实现比如eProsima Fast DDS、Eclipse Cyclone DDS、RTI Connext DDS等。FastDDS因为ROS 2默认支持的原因使用率很高CycloneDDS在嵌入式性能上表现不错RTI在汽车和国防行业有很多商业案例。自动驾驶公司经常会自己封装一层把底层具体哪家DDS屏蔽掉保留统一的Topic接口。SOME/IP的开源方案主要有vSOME/IP、CommonAPI SOME/IP也是当前学习成本最低的入门选择。vSOME/IP比较接近量产形态支持SD、RPC、事件组、TCP/UDPCommonAPI则提供了更高层的C绑定用代码生成器生成Proxy和Stub。AUTOSAR AP也带了SOME/IP的协议栈但商用授权和配置流程更重。从生态看DDS的社区资料多源于机器人领域SOME/IP的资料多在AUTOSAR和传统ECU开发圈子。你在搜索引擎输“DDS”的时候可能还会看到DDS thumbnail viewer这类图像工具又是另一个缩写语境。跨行业看资料时建议带上“自动驾驶”“通信中间件”这些限定词再搜。3. 场景选型与协同落地实操3.1 自动驾驶模块的通信需求拆解把一辆L2或L3级自动驾驶汽车的软件模块大致分成感知、融合、预测、规划、控制五类再加上诊断和OTA感知模块产生原始点云、图像、目标列表。数据量大单帧点云可能几十兆比特更新频率在10Hz到30Hz。这种数据流适合DDS Best Effort 大Topic 共享内存传输。融合和预测模块通常订阅感知结果输出融合轨迹和预测目标。数据量比原始数据小但对时序一致性要求高适合DDS Reliable Deadline策略。规划模块输出轨迹点和控制意图可能有多个下游节点同时订阅。DDS的一对多分发很合适。控制模块最终要把轨迹转成转向、油门、制动的指令。指令面向车辆执行器常常通过SOME/IP的Method或者Event发给车辆控制器。诊断和OTA服务就更偏向SOME/IP了这些本来就是服务调用场景和AUTOSAR的SOA模型天然匹配。一个常见的误区是“DDS性能比SOME/IP好所以全车用DDS就行”。实际上很多车辆控制指令并不需要几十Hz的发布频率而需要极强的确定性和服务语义用SOME/IP反而更容易融入既有的AUTOSAR工具链。3.2 搭建一个最小DDS通信样例做实验可以先用FastDDS跑通一个最小发布订阅。下面是一个极简的发布端示意重点看三层创建Participant、创建Topic、创建Writer并发送。#include fastdds/dds/domain/DomainParticipantFactory.hpp #include fastdds/dds/topic/DataTypeSupport.hpp #include fastdds/dds/pub/Publisher.hpp #include fastdds/dds/pub/DataWriter.hpp class MyData { public: uint32_t seq; double x; double y; }; // 注册类型(simplified) class MyDataPubSubType : public eprosima::fastdds::dds::TopicDataType { // 实现 serialize/deserialize/create_data ... }; eprosima::fastdds::dds::DomainParticipant* participant eprosima::fastdds::dds::DomainParticipantFactory::get_instance()-create_participant(0); eprosima::fastdds::dds::Topic* topic participant-create_topic(LaneBoundary, type, eprosima::fastdds::dds::TOPIC_QOS_DEFAULT); eprosima::fastdds::dds::DataWriter* writer participant-create_publisher().create_datawriter(topic, eprosima::fastdds::dds::DATAWRITER_QOS_DEFAULT); MyData data{1, 0.5, 0.2}; writer-write(data);订阅端类似关注的是DataReader收到的数据。实际工程里QoS不要用默认值至少要根据场景配置RELIABILITY和DURABILITY。比如路沿线检测结果需要新节点加入时立即收到最新一帧DURABILITY就配成TRANSIENT_LOCAL否则新节点要等下一帧才看到数据可能在启动阶段造成功能缺口。有一点我踩过坑DDS的Domain ID范围有限默认配置下Participant在同一个Domain内才可见。如果把不同功能模块放到了不同Domain它们之间的Topic是物理隔离的不要指望通过Partition打通。只有同一Domain下的Partition才能做逻辑隔离。3.3 搭建一个最小SOME/IP通信样例用vSOME/IP搭建一个服务端核心是创建application、提供service、注册method回调。简化代码如下#include vsomeip/vsomeip.hpp std::shared_ptrvsomeip::application app vsomeip::runtime::get()-create_application(service_demo); // 服务端注册 app-init(); app-register_message_handler(0x1234, 0x5678, [](const std::shared_ptrvsomeip::message req) { auto resp vsomeip::runtime::get()-create_response(req); resp-set_payload(req-get_payload()); app-send(resp); }); app-offer_service(0x1234, 0x5678); app-start();客户端调用Method时先注册availability回调等服务上线后再发送请求app-register_availability_handler(0x1234, 0x5678, [](..., bool is_available) { if (is_available) { auto req vsomeip::runtime::get()-create_request(false); req-set_service(0x1234); req-set_instance(0x5678); req-set_method(0x0001); app-send(req); } });SOME/IP的端口和服务ID不是随便定的需要在系统设计文档里统一管理。项目里如果多人并行开发最好有一个接口ID分配表类似“0x12340x0001代表XX控制器读取状态”否则后期联调会出现消息错乱。3.4 网关桥接让两种协议在同一条链路上配合协同架构可以设计成一个“DDS域 SOME/IP服务 网关转换层”的模式。假设上游感知和规划跑在DDS域内感知结果Topic叫PerceptionTargetList规划轨迹Topic叫PlannedTrajectory。控制指令需要下发给VCUVCU侧是AUTOSAR服务环境能理解SOME/IP。网关节点同时加入DDS域和SOME/IP网络它订阅PlannedTrajectory收到轨迹后转换成SOME/IP Field或者Event再发给VCUVCU返回的状态再用SOME/IP Method取回转换成DDS Topic发布给规划模块。这个方案的好处是域内大数据不受影响DDS可以保持高频率低抖动整车控制接口则保持SOME/IP的服务语义符合AUTOSAR这边的开发习惯。网关需要解决的是协议转换过程中的时效和数据一致性转换层最好做“最新值缓存”DDS一帧到达后立刻更新缓存SOME/IP侧基于缓存值发送避免每个控制周期都在线程间做阻塞式等待。我在一个实际项目里就是这么做的感知和规划用DDS车辆控制用SOME/IP网关进程里跑了两个独立线程池通过无锁队列交换数据。实测下来控制周期稳定在10ms左右DDS侧的点云流并发打到网络也不会拖垮SOME/IP链路。关键点在于给两条链路分配独立的网络线程和发送缓冲区别混在一个队列里。3.5 关键参数与性能调优建议先说DDS侧。大数据Topic建议打开共享内存传输SHMFastDDS和CycloneDDS都有类似机制。共享内存不是解决一切问题的银弹跨ECU还是要走网络但在单机多进程场景下零拷贝能减少大量CPU拷贝开销。QoS里的History配置也影响内存KeepAll对高频传感器数据可能把内存吃满一般用KeepLast即可。SOME/IP侧主要关注SD周期。启动阶段服务要快速被发现SD报文可以每隔100ms发几次稳定运行后把周期拉长到1s左右减少网络上的心跳噪声。订阅事件组时事件发送方式可以选周期通知或变化即通知。车辆状态类数据建议两种结合状态变化时立即发同时保留一个低频兜底周期防止订阅方因遗漏变化而长时间拿到旧值。另外建议把DDS的Domain和SOME/IP的Service ID都纳入配置管理不要写死在代码里。配置外部化以后硬件在环测试、实车联调、仿真回放可以走同一套构建产物只换配置文件和网络拓扑。这样调试效率会高很多。4. 常见问题与排查技巧实录4.1 DDS跨网段不通端口总是“飘”DDS基于RTPS默认自动发现使用多播和一部分临时端口。如果部署环境只开放了固定端口或者路由器禁止组播经常会出现“Topic看到但数据不通”的诡异现象。排查时先确认多播组是否通再确认防火墙放行了正确端口。实际项目里建议把DDS的发现模式改成“静态发现”或“Discovery Server”。使用Discovery Server后Participant只要知道Server地址即可不依赖组播跨网段和容器部署稳定很多。这个配置往往能直接解决端口不固定的痛苦代价是需要维护一个Server节点作为中心发现服务。4.2 SOME/IP服务发现“忽上忽下”如果SOME/IP服务的SD周期设计不合理比如每次变化都会触发OfferService重发在高频状态下会产生大量重复报文网络一忙就会丢包。症状表现为客户端有时能发现服务有时不能。优化方案是把“瞬态SD报文”和“周期SD报文”分开看服务地址变化、网络切换时立即广播其他时间用固定周期维持。另外SD报文一般很小但量大也会造成CPU中断过高建议在嵌入式平台上评估一下每秒钟的被唤醒次数。我见过有人把SD周期配到50ms结果一个控制域里几十个服务每秒钟上千条SD报文来回飞完全没必要。4.3 两个协议栈一起跑线程和锁打架DDS和SOME/IP都有自己的接收线程和发送线程如果两个协议栈放在同一个进程里主循环可能因为锁竞争导致延迟漂移。尤其是DDS的Writer内部队列、SOME/IP的SD定时器如果共享同一个线程池一个出问题就会拖慢另一个。我的经验是两个协议栈各自独立线程绑定不同的CPU核心中间用无锁队列做交换。另外注意优先级不要混用DDS的实时数据线程可以设置高优先级SOME/IP的服务响应线程保持普通优先级。否则一次慢的Method调用可能导致数据分发的周期抖动。4.4 抓包时看到的“DDS”不是同一个DDS做通信调试时Wireshark有RTPS dissector和SOME/IP dissector可以直接解析协议通用做法是抓UDP包后按协议过滤。抓包时如果看到很多组播包大概率是DDS的发现和数据分发看到单播的周期性小包多半是SOME/IP的SD。这里再强调一次简称问题网上搜“DDS thumbnail viewer”搜出来的是图像格式缩略图查看器搜“DDS IP核”“DDS芯片”搜出来的是直接数字频率合成的硬件方案。它们和自动驾驶里的DDS通信中间件完全是两回事。哪怕是同一个缩写“DDS”放在FPGA、射频、图像格式、分布式通信四个语境里含义天差地别查资料时一定要带“Data Distribution Service”或者“自动驾驶”限定词。4.5 数据集回放和仿真中的通信栈验证很多团队会用开源自动驾驶数据集来验证感知算法比如nuScenes、Waymo Open Dataset。用DDS回放数据也很常见把数据集转成DDS消息按原始时间戳发布算法节点直接订阅即可。这里要注意时间同步DDS消息里最好带上采集时间戳否则回放速度和算法处理速度脱节会导致缓存堆积。仿真层面一些团队也用游戏级模拟环境做通信栈验证比如基于《欧洲卡车模拟2》这类游戏改装的自动驾驶插件或者CARLA仿真平台。这类环境可以用来测一测DDS/SOME/IP的消息流是否正确、数据是否按预期到达但要注意仿真里的时钟和网络特性和实车不一样不要拿仿真延迟直接推导量产性能。通信中间件的延迟、抖动、CPU占用还是要在真实以太网环境里做硬件在环测试才有说服力。4.6 实测下来比较顺手的排查顺序遇到通信问题我一般按这个顺序排查先看协议栈日志确认节点是否互相发现再看抓包确认报文是否到达网卡再看丢包率和延迟直方图最后定位到具体是发现问题还是数据面问题。很多“数据卡顿”的真相其实是拓扑切换后节点没有重新发现而不是数据面丢了这时候抓包看RTPS的SPDP心跳重发会非常直观。5. 个人选择思路与几个小建议做项目选型时不要先问“DDS好还是SOME/IP好”先问“这个模块是一堆数据还是一组服务”。感知融合、规划轨迹、目标列表这类高频数据我会优先选DDS诊断、配置、整车控制、OTA这类需要服务语义和AUTOSAR生态对齐的接口我优先选SOME/IP。当两种需求都出现在同一台域控里就做好网关桥接和线程隔离。通信协议栈不是写完功能就完事的配置项多到让人头疼。我自己的做法是建一个“通信设计checklist”把Topic列表、QoS参数、Domain ID、Partition规划、服务ID表、SD周期、端口策略全部维护在配置里每次联调前先过一遍。很多问题其实都是配置文件不一致引起的而不是协议本身的问题。最后再分享一个小技巧在开发早期就把DDS的Topic命名和SOME/IP的服务ID统一纳入CI检查。比如代码提交时自动跑一遍格式校验看看发布端和订阅端的Topic是否一致服务ID是否重复。这个习惯帮我们省掉了很多联调现场“你发的是这个名字我订阅的是另一个名字”的低级错误。协议选型很重要但真正决定量产项目顺不顺的往往是这些容易被忽略的工程细节。
返回列表