ROS之topic和service通信比较

本文对比分析了ROS中topic与service两种通讯方式的实际通讯效率,揭示了两者在不同应用场景下的优势与劣势,并提供了详细的实验数据支撑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实验速度

1. via topic

同一台机器上通过topic传输数据用时(单位/s)

上图是以前ROS课上做的一个实验,内容是测试一个publisher和一个subscriber之间通讯所用的时间。两个node都很简单,publisher发送一个字符串,字符串带有标号;subscriber回显该字符串,字符串长度不超过20个char。

怎么去标定发送时间是接收时间呢?目前使用的方法就是在publisher发送前使用ROS_INFO输出一个消息,消息会带有ROS的时间戳;subscriber的callback函数里边也使用ROS_INFO输出一个带时间戳的消息。虽然说这种方法并不是非常精准,但目前也没有想到更好的办法了,哪怕是使用header里边的time stamp也有一个获得当前时间和赋值的过程,难以百分百精确,所以目前只能这样粗略测量。

根据实验数据可以发现在同一台机器上,两个node通过topic通讯,大致产生0.7ms的延迟。(本人机子i5-3210M/4G)

2. via service

同一台机器上client call server后server接收到数据用时(单位/s)

这张图是今天自己做的测试,可以看到平均时间大约在3ms左右。

测量的方法也是跟上面的类似。Client在call之前先输出一个带时间戳的消息,server在接收到请求执行操作前也会输出一个带时间戳的消息。这次client与server之间传递的消息更短,是两个int。

从上图我们不难发现,通过service通讯居然比topic延迟要高?实在是不愿意相信,因为这跟我一开始的理解是相违背的。为了控制实验环境平台的一致性(我从groovy换到了indigo),于是重做了以前的那个实验,粗测通过topic传输的延迟时间,发现依然是0.7ms左右。


理论速度

为何前面的实验结果让我如此惊讶?

官方文档中写明,client与server之间维持着一个持久连接。对于这种RPC请求/回复机制,官方给出的评价是“面对低鲁棒性的服务程序变更有着更好的性能表现”(higher performance at the cost of less robustness to service provider changes)1

说到持久连接,不禁让人想起ROSTCP。照这么说,如果在ROS架构下node之间通讯的底层实现都是通过ROSTCP/ROSUDP的话,那理应via service应该跟via topic的速度相当,如果将“持久性”纳入考虑范围的话,service甚至应该比topic更快一些才对。但实际情况是via service的时延是via topic的4倍左右。如此大的差距,实在让人百思不得其解。


实现机制——同步与异步

其实在开始写这篇博文的时候,我都依然没有想明白这个问题。边写边查资料,看到ros answer上一位大大在描述这两种机制时,用到了asynchronous和synchronous这两个字眼2。回到宿舍一边洗着冷水澡一边在想,忽然灵光一现!是的,实验的结果是没有问题的,有问题的是我的实验方法!

- Asynchronous Topic

Topic是异步的。简简单单的一句话,里边却隐含了千言万语。让我关注起这一点的原因除了ros answer上面的那篇问答,还有一个就是自己做的另一个实验:创建一个publisher,pub的速度是100Hz,发送缓冲区的大小只有1;创建一个subscriber,调用callback函数的的速度是1Hz(用spinOnce),但接收缓冲区的大小是1000。这样的效果就是,publisher近乎匀速地发送着数据,每秒100次;而subscriber每一秒调用一次callback函数(spinOnce),每次调用都一次性地处理接收缓冲区内的所有数据。由于接收缓冲区大小远大于发送频率,所以接收缓冲区不存在满的情况,所以就能看到subscriber每秒都会很快地处理完100条消息,然后进入休眠,再被唤醒执行callback,再休眠……

而之前用于测试的subscriber,我使用的是spin。这也就意味着,回调函数一直是处于类似于忙等待的状态的,一旦发现接收缓冲区内有数据,即刻取出作运算(前提当然是获得CPU分片时间)。这也就说明了为什么实验得出的结果via topic会更快!但要注意了,忙等待是会占用硬件资源的。在这种简单的实验条件下,via topic固然是很快,但当一个大项目跑起来时,它之前的高性能表现可能要大打折扣。

- Synchronous Service

至于server,在基于RPC请求/回复机制的前提下,它在接收到调用请求前都是处于休眠状态的。Client的call请求将server唤醒,然后server执行请求,再返回结果给client。所以之前在计时的时候,大部分的时间都是消耗在将server从休眠状态唤醒上了!所以才会看起来比via topic慢。

分析到这里,我就在想,那真正的client与server之间通讯的时延,是不是可以利用server返回结果给client这段时间算出呢?这才是真正抛开了将server从休眠唤醒的时间影响,是client与server通讯的真正时延!于是又有了下图: 
同一台机器上server返回结果后client接收到数据用时(单位/s)

果不其然!表中数据的中位数应该在0.3ms左右,也就是说,从server返回结果到client接收到结果,只需要0.3ms的时间!尽管使用spin通过topic转发数据类似于忙等待(也许ROS的内部机制会对spin的频率作最高限制),但也需要大约0.7ms的时间!而service机制中,client发出call请求之后就会进入阻塞状态,直至server返回结果,期间的时延是via topic的一半而已!

而从这个数据我们还可以估计出将休眠状态的server唤醒,所需要的时间大约是2.7ms。如果计算一来一回,client从发出call请求到接收返回结果,除去请求被执行的时间,在通讯上耗费的时间约是3.3ms,其中将server唤醒的时间约占82%!

- Conclusion

Topic与Service各有优劣。在设计项目的时候,要周全考虑各个方面的因素。Service比较适合用于执行复杂的、调度次数较低的任务。而topic则适合在通讯频率高的情况下使用。再者,使用ros::Rate和sleep合理设置程序的执行速率,能使程序弹性更大,增强可维护性。使用topic时,也别忘了关注一下缓冲区的大小设置。


优劣分析

Service

- 优点:
  • Client只需要关注发出命令请求、接收反馈,而无须关注底层实现,系统可维护性高
  • Client维护着一个与server的持久连接(persistent connection),在单纯的数据传输上可以做到更快,尤其是在分布式作业时
- 缺点:
  • 当server单方改变执行模式或者存在bug的时候,client无法得知具体情况,因为server程序对client而言是透明的。在这种情况下,client只能得到一个错误的数据,或者被告知执行失败,甚至是等待超时
  • Server唤醒耗时太大,不建议用于通讯频率高的情况

Topic

- 优点:
  • 能非常方便地通过监听topic的方法查看各个node的运行状态等信息,可维护性高
  • 能根据实际情况调整发送速率、发送/接收缓冲区大小,使之适应项目需求
  • Topic是多对多的,可操作性强;而service只能一对多(一个server应对多个client)
- 缺点:
  • 因为是异步架构,回调函数的执行频率设计对系统整体性能影响很大
  • 对于使用频率不高的程序,若使用topic进行信息交换,想要减少额外系统开销的办法就是降低回调的速率。但一旦降低回调的速率,同时受到负面影响的还有系统的实时性

  1. Wiki of ROS services: http://wiki.ros.org/Services 
  2. ROS Answer - design question: topic vs service?http://answers.ros.org/question/188354/design-question-topic-vs-service/ 
ROS(Robot Operating System)是一个为机器人开发提供服务的开源框架。ROS中的通信机制主要包括TopicServiceAction三种,其中Topic是最常用的通信方式之一。Topic通信是一种单向的、异步的通信机制,适用于发布-订阅模式。 在ROS中,Topic通信的基本流程如下: 1. **发布者(Publisher)**:发布者负责发布消息到一个特定的Topic。发布者可以通过`ros::Publisher`类来创建管理发布过程。 2. **订阅者(Subscriber)**:订阅者负责订阅特定的Topic并接收消息。订阅者可以通过`ros::Subscriber`类来创建管理订阅过程。 3. **消息(Message)**:消息是发布者订阅者之间传输的数据。ROS提供了多种标准消息类型,用户也可以自定义消息类型。 以下是一个简单的ROS Topic通信示例: ### 发布者代码(publisher.cpp) ```cpp #include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> int main(int argc, char **argv) { ros::init(argc, argv, "talker"); ros::NodeHandle n; ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); ros::Rate loop_rate(10); int count = 0; while (ros::ok()) { std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str()); chatter_pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); ++count; } return 0; } ``` ### 订阅者代码(subscriber.cpp) ```cpp #include "ros/ros.h" #include "std_msgs/String.h" void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); } int main(int argc, char **argv) { ros::init(argc, argv, "listener"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); ros::spin(); return 0; } ``` ### 编译运行 1. 在`CMakeLists.txt`中添加以下内容: ```cmake find_package(catkin REQUIRED COMPONENTS roscpp std_msgs ) catkin_package() include_directories( ${catkin_INCLUDE_DIRS} ) add_executable(publisher src/publisher.cpp) target_link_libraries(publisher ${catkin_LIBRARIES}) add_executable(subscriber src/subscriber.cpp) target_link_libraries(subscriber ${catkin_LIBRARIES}) ``` 2. 编译: ```sh catkin_make ``` 3. 运行: ```sh roscore rosrun <package_name> publisher rosrun <package_name> subscriber ``` 通过上述示例,可以看到ROSTopic通信的基本实现方法。发布者发布消息到指定的Topic,订阅者订阅该Topic并接收消息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值