国外做问卷赚购物券等的网站,刷业务网站怎么做,互联网创业就是做网站吗,普陀专业做网站1. 可靠性#xff08;RELIABILITY#xff09;#xff1a;你的数据能“说到做到”吗#xff1f; 大家好#xff0c;我是老张#xff0c;在工业控制和机器人领域摸爬滚打了十几年#xff0c;用过不少通信中间件。今天想和大家聊聊CycloneDDS里的QoS#xff08;服务质量 // 1. 创建一个使用“可靠模式”的QoS dds::pub::qos::PublisherQos pub_qos; // 发布者QoS通常用默认就行 dds::sub::qos::SubscriberQos sub_qos; // 订阅者QoS // 重点配置Topic的QoS dds::topic::qos::TopicQos reliable_topic_qos; // 设置可靠性为RELIABLE最大阻塞时间2秒 reliable_topic_qos dds::core::policy::Reliability::Reliable( dds::core::Duration::from_sec(2)); // 创建一个使用此QoS的Topic dds::topic::TopicMyDataType topic(participant, MyReliableTopic, reliable_topic_qos); // 创建DataWriter继承Topic的QoS dds::pub::DataWriterMyDataType writer( dds::pub::Publisher(participant, pub_qos), topic); // 2. 创建一个使用“尽力而为模式”的QoS dds::topic::qos::TopicQos best_effort_topic_qos; best_effort_topic_qos dds::core::policy::Reliability::BestEffort(); dds::topic::TopicMyDataType topic_be(participant, MyBETopic, best_effort_topic_qos); dds::pub::DataWriterMyDataType writer_be( dds::pub::Publisher(participant, pub_qos), topic_be); std::cout 可靠和尽力而为的Writer创建完成 std::endl; return 0; }注意可靠通信要求Publisher和Subscriber两侧的实体Topic、DataWriter、DataReader的可靠性QoS是兼容的。通常一端的模式是另一端的“超集”才能匹配。最常见的就是两端都设为RELIABLE或者一端RELIABLE另一端BEST_EFFORT此时按BEST_EFFORT运行。如果一端是BEST_EFFORT另一端是RELIABLE匹配会失败。所以最佳实践是在设计阶段就约定好每个Topic的可靠性要求。2. 历史HISTORY与持久性DURABILITY新读者如何拿到“旧报纸”聊完了数据怎么保证送到我们来看另一个经典问题一个新加入的订阅者能不能看到它加入之前发布的数据比如一个监控面板程序后启动它需要立刻显示当前系统的温度、压力等状态而不是傻傻地等着下一个数据到来。这就需要HISTORY历史和DURABILITY持久性这两个策略联手解决了。你可以把DataWriter想象成一个报社DataReader是订户。HISTORY策略决定了报社的“稿件库”有多大能存多少期报纸。而DURABILITY策略决定了这个稿件库是“内存里的便签”断电就丢还是“硬盘里的档案”永久保存。2.1 HISTORY策略你的缓存有多深HISTORY策略主要控制DataWriter和DataReader本地缓存多少数据样本。它有两个关键参数kind种类和depth深度。kind有两种主要类型KEEP_LAST只保留最新的N个样本。这是最常用的模式用一个滑动窗口来管理缓存。KEEP_ALL保留所有收到的样本直到资源如内存耗尽。这个要慎用对于高频数据很容易把内存撑爆。depth参数就是当kindKEEP_LAST时那个“N”是多少。我举个例子一个GPS模块每秒发布10次位置数据。如果DataReader的HISTORY策略是KEEP_LASTdepth5那么这个DataReader的本地缓存里始终只保留最新的5个位置数据即最近0.5秒的数据。新数据到来最旧的数据就被挤出去了。这个策略在哪里最有用在数据消费端处理速度可能跟不上生产端速度的时候。比如你的DataWriter以100Hz发送数据但DataReader端的处理算法比较复杂可能只能以50Hz处理。如果没有缓存一半的数据就丢了。设置了KEEP_LAST和合适的depth处理线程就可以从容地从缓存里取数据避免丢失。2.2 DURABILITY策略数据能“活”多久HISTORY解决了本地缓存多少数据而DURABILITY解决了这些缓存数据的“生存范围”和“持久化”问题。它定义了数据样本的“生存期”和“可达性”。CycloneDDS提供了几个级别的持久性VOLATILE易失的最低级别。数据只存在于内存中且只对“同时在线”的DataReader可见。一个新来的DataReader拿不到任何历史数据。这就像两个人面对面说话后来的人听不到之前的对话。TRANSIENT_LOCAL瞬态本地这是实现“新读者读旧数据”最常用的级别DataWriter会为它发布的每个Topic在本地维护一个历史缓存大小由HISTORY的depth决定。当一个DataReader加入时DataWriter会把这个缓存里的数据“推”给新的DataReader。但注意这个缓存只在DataWriter存活时存在。DataWriter进程退出缓存就没了。TRANSIENT瞬态的比TRANSIENT_LOCAL更强。缓存由DDS的“持久化服务”如果配置了或DomainParticipant在全局维护即使原始的DataWriter退出了只要DomainParticipant还在新加入的DataReader仍然能拿到历史数据。这需要额外的服务支持。PERSISTENT持久的最高级别数据会持久化到磁盘。即使整个DDS域的所有程序都重启新启动的DataReader也能拿到之前发布的数据。这相当于一个内置的数据库了开销也是最大的。在99%的工业场景下TRANSIENT_LOCAL配合KEEP_LAST的HISTORY就足以满足“新订阅者获取最新状态”的需求了。2.3 组合使用实战让后启动的监控端立刻显示数据让我们写一段代码实现一个经典场景一个数据发布者比如模拟传感器和一个可能后启动的数据订阅者比如监控GUI。我们要确保GUI一启动就能看到当前的最新值。发布者端代码// publisher.cpp #include dds/dds.hpp #include SensorData.hpp #include thread int main() { dds::domain::DomainParticipant participant(0); dds::pub::Publisher publisher(participant); // 配置Topic QoS持久性为TRANSIENT_LOCAL历史为KEEP_LAST深度为10 dds::topic::qos::TopicQos topic_qos; topic_qos dds::core::policy::Durability::TransientLocal() dds::core::policy::History::KeepLast(10); // 缓存最近10个样本 dds::topic::TopicSensorData topic(participant, SensorTopic, topic_qos); // DataWriter继承Topic的QoS dds::pub::DataWriterSensorData writer(publisher, topic); SensorData sample; for (int i 0; i 100; i) { sample.value(static_castfloat(i)); writer.write(sample); std::cout 写入数据: sample.value() std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 每秒发一次 } return 0; }订阅者端代码// subscriber.cpp #include dds/dds.hpp #include SensorData.hpp int main() { dds::domain::DomainParticipant participant(0); dds::sub::Subscriber subscriber(participant); // **关键订阅者的QoS必须与发布者兼容。持久性至少也要是TRANSIENT_LOCAL才能接收到历史数据。** dds::topic::qos::TopicQos topic_qos; topic_qos dds::core::policy::Durability::TransientLocal() dds::core::policy::History::KeepLast(10); dds::topic::TopicSensorData topic(participant, SensorTopic, topic_qos); dds::sub::DataReaderSensorData reader(subscriber, topic); // 等待与发布者建立连接并获取历史数据 std::this_thread::sleep_for(std::chrono::seconds(2)); // 读取数据 auto samples reader.take(); for (const auto sample : samples) { if (sample.info().valid()) { std::cout 收到历史/实时数据: sample.data().value() std::endl; } } // 进入循环持续接收新数据 while (true) { auto new_samples reader.take(); for (const auto s : new_samples) { if (s.info().valid()) { std::cout 收到新数据: s.data().value() std::endl; } } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } return 0; }你可以先运行发布者等它发几条数据后再运行订阅者。你会看到订阅者启动后第一次调用reader.take()时不仅能拿到当前的新数据还能一次性拿到发布者缓存里的最近10条历史数据如果发布者已经发了超过10条。这就是TRANSIENT_LOCAL和KEEP_LAST的魔力。3. 截止时间DEADLINE与基于时间的过滤TIME_BASED_FILTER给数据流加上“节拍器”在实时系统里数据不仅不能丢还得按时来。比如一个机器人控制器需要每10毫秒收到一次关节反馈数据如果数据迟到控制器就可能做出错误决策。CycloneDDS提供了两个与时间强相关的QoS策略来管理这种时序要求DEADLINE截止时间和TIME_BASED_FILTER基于时间的过滤。3.1 DEADLINE给数据生产消费立规矩DEADLINE策略为DataWriter和DataReader之间的数据流设定了一个预期的“心跳”间隔。它不是一个强制性的约束不会让数据发不出去而是一个监控和告警机制。工作原理是这样的你在DataWriter端设置一个period周期比如100毫秒。这等于向系统宣告“我承诺每100毫秒至少发布一次这个Topic的数据。” 同时在DataReader端也设置一个相同或兼容的period。这等于订阅者期望“我期望每100毫秒至少收到一次这个Topic的数据。”DDS底层会默默地帮你计时。如果DataWriter超过承诺的周期还没发新数据或者DataReader超过期望的周期还没收到新数据DDS就会触发一个DEADLINE_MISSED状态变更。你的应用程序可以监听这个状态然后采取相应措施比如记录告警、切换备用数据源、或者触发系统安全状态。这个功能在健康诊断和系统监控中特别好用。我把它比作数据的“心跳检测”。配置代码如下// 设置DEADLINE QoS期望周期为100毫秒 dds::core::Duration deadline_period(0, 100000000); // 100毫秒 auto deadline_qos dds::core::policy::Deadline(deadline_period); // 应用于Topic确保读写两端一致是最佳实践 dds::topic::qos::TopicQos topic_qos; topic_qos deadline_qos dds::core::policy::Reliability::Reliable(); // 通常与可靠模式一起用 dds::topic::TopicControlCommand topic(participant, CmdTopic, topic_qos); // 创建DataReader后可以设置监听器来监听DEADLINE_MISSED事件 dds::sub::DataReaderControlCommand reader(subscriber, topic); // ...此处需设置DataReader监听器重写on_requested_deadline_missed方法...当DataReader检测到超过100ms没收到新数据on_requested_deadline_missed回调函数就会被调用你可以在里面处理异常。3.2 TIME_BASED_FILTER给数据消费端“降采样”如果说DEADLINE是管生产端“别太慢”那TIME_BASED_FILTER就是管消费端“别太快”。它的主要目的是防止DataReader被高速数据淹没为消费端提供一个“降采样”的机制。想象一下一个激光雷达以100Hz发布点云数据但你的路径规划算法只需要20Hz的数据进行运算。如果你直接订阅算法线程会被频繁唤醒处理大量冗余数据浪费CPU。这时在DataReader上设置TIME_BASED_FILTER就完美了。它的核心参数是minimum_separation最小分离时间。你把它设置为50毫秒对应20Hz。那么无论发布者发得多快DataReader最多每50毫秒才会将一个数据样本递给你的应用层回调函数。中间到来的数据会被过滤掉。这里有个巨大误区要澄清TIME_BASED_FILTER过滤的是DataReader通知应用程序的速率而不是网络接收速率。DataReader底层仍然在接收每一个数据包前提是可靠性等策略允许并更新其内部缓存最新值。它只是控制着“多久去缓存里取一次数据给你用”。// 在DataReader端设置基于时间的过滤最小间隔50毫秒 dds::core::Duration min_separation(0, 50000000); // 50毫秒 auto time_filter_qos dds::core::policy::TimeBasedFilter(min_separation); dds::sub::qos::DataReaderQos reader_qos; reader_qos time_filter_qos dds::core::policy::Reliability::Reliable(); dds::sub::DataReaderLidarData reader(subscriber, topic, reader_qos);配置好后即使激光雷达数据以100Hz10毫秒间隔涌来你的on_data_available回调函数也只会以20Hz的频率被触发大大减轻了处理负担。这个策略在对接高频传感器和低频处理模块时是简化系统设计的利器。4. 目标顺序DESTINATION_ORDER多源数据先来后到怎么算在复杂的分布式系统中一个Topic可能有多个DataWriter在同时发布数据。比如一个“系统状态”Topic可能由导航、控制、电源三个模块同时发布其子状态。那么对于订阅这个Topic的DataReader来说它收到数据的顺序应该由什么决定这就是DESTINATION_ORDER目标顺序策略要解决的问题。这个策略只有DataReader需要关心。它提供了两种主要排序规则BY_SOURCE_TIMESTAMP默认值也是最常用的。按照DataWriter写入数据时打上的源时间戳进行排序。这保证了数据的“因果顺序”即先发生的事件先被处理无论网络延迟如何。在上面的例子中如果导航模块在时刻T1发布状态电源模块在时刻T2T2 T1发布状态即使电源模块的数据包先到达DataReader也会先处理导航模块在T1的数据。BY_RECEPTION_TIMESTAMP按照DataReader实际接收到数据的时间戳进行排序。这反映了数据到达的“真实顺序”。在某些对绝对处理顺序要求不高但需要尽快处理最新到达数据的场景下可以使用。继续上面的例子如果电源模块的数据包先到那就先处理电源模块的状态。我强烈建议在绝大多数情况下使用默认的BY_SOURCE_TIMESTAMP。因为它能保证逻辑上的正确性。考虑一个订单处理系统订单创建消息T1必须早于订单付款消息T2被处理。如果使用接收顺序网络抖动可能导致付款消息先到从而引发逻辑错误。配置非常简单通常你不需要改它用默认值就好。但如果你需要显式设置dds::sub::qos::DataReaderQos reader_qos; // 使用源时间戳排序默认 reader_qos dds::core::policy::DestinationOrder::BySourceTimestamp(); // 或者使用接收时间戳排序 // reader_qos dds::core::policy::DestinationOrder::ByReceptionTimestamp();注意DESTINATION_ORDER策略只对启用了“有序”访问的DataReader有效这通常由PRESENTATIONQos策略控制且默认是无序的。如果你的应用严格要求多个DataWriter之间的数据有全局顺序你可能还需要配置PRESENTATION策略的access_scope为GROUP或INSTANCE但这属于更高级的用法会带来性能开销需要谨慎评估。5. 总结与避坑指南如何设计你的QoS策略矩阵好了我们把CycloneDDS最核心的几个QoS策略过了一遍。最后我不喜欢空谈总结更想分享一套我在实际项目中总结出来的“QoS策略选择决策流”和避坑点你可以直接拿去参考。第一步确定数据的“价值”和“时效性”控制指令、状态切换、报警信号这些数据必须万无一失且顺序不能错。首选RELIABLEBY_SOURCE_TIMESTAMP默认。max_blocking_time根据系统实时性要求设置通常1-5秒。高频传感器数据IMU、视觉特征点、激光扫描点数据流大每个样本独立最新值最重要。首选BEST_EFFORT。可以搭配TIME_BASED_FILTER给消费端降频。系统状态、配置参数需要让后加入的模块能立刻获取。必须使用DURABILITY为TRANSIENT_LOCALHISTORY为KEEP_LASTdepth设为1或一个较小的值只传递最新状态。第二步考虑生产消费的节奏如果生产速度远快于消费速度在DataReader端使用TIME_BASED_FILTER避免过载。如果数据必须有规律地到达在DataWriter和DataReader两端设置DEADLINE进行心跳监控。几个我踩过的坑QoS不匹配导致无法通信这是新手最常见的问题。记住一个核心原则DataWriter和DataReader的QoS必须“兼容”才能建立连接。兼容的规则通常是“请求的 提供的”。比如DataReader要求RELIABLEDataWriter是BEST_EFFORT则不兼容。DataReader要求TRANSIENT_LOCAL持久性DataWriter是VOLATILE也不兼容。最省事的办法是为每个Topic定义好一个QoS配置文件发布和订阅双方都使用完全相同的QoS。KEEP_ALL历史策略导致内存泄漏除非你非常清楚自己在做什么并且有完善的生命周期管理否则不要轻易使用KEEP_ALL。对于持续不断的数据流它会无限缓存直到耗尽内存。KEEP_LAST配合一个合理的depth是更安全的选择。过度使用RELIABLE影响性能可靠传输带来ACK和重传开销在高频数据传输场景下会显著增加延迟和CPU占用。对于可以容忍偶尔丢失的数据流勇敢地使用BEST_EFFORT系统会更轻盈。忽略了DEADLINE的监听设置了DEADLINE却没实现监听回调那就白设了。记得在DataReader上实现on_requested_deadline_missed回调函数在里面添加你的诊断或容错逻辑。配置QoS不是一蹴而就的它需要在系统设计初期就纳入考量并在后期根据实际网络环境和性能测试进行微调。最好的办法是为你的每个数据Topic建立一个QoS配置表写清楚选择每个策略的原因和参数值这会让团队协作和后期维护轻松很多。希望这些实战经验能帮你绕过那些我当年趟过的坑更高效地用好CycloneDDS这个强大的工具。