2. ROS通信架构:Master与Node的“社交网络”
好,咱们进入正题。ROS的通信架构,说白了就是一堆机器人程序之间怎么“说话”的问题。你想想看,一个机器人身上有激光雷达、摄像头、电机控制器,每个部件都在跑不同的程序。这些程序怎么协调工作?靠的就是ROS这套通信机制。
我个人习惯把ROS的通信架构比作一个“社交网络”。Master是社交平台,Node是用户,Topic是朋友圈,Service是私信,Action是带进度条的长任务。嗯,这么一比喻,是不是好理解多了?
2.1 ROS Master与Node:谁管着谁?
先说说Master。Master是ROS的“大管家”,它负责帮各个Node找到对方。每个Node启动时,都会去Master那里“报到”,说:“我叫/talker,我要发一个叫/chatter的话题。”或者“我叫/listener,我想听/chatter话题。”
Master干的事很简单:登记和匹配。它不负责传输数据,只负责牵线搭桥。一旦两个Node建立了连接,Master就退出了,数据直接点对点传输。
核心要点:Master是“中介”,不是“传话筒”。数据不走Master,走的是Node之间的直连通道。
我记得刚学ROS时,有个坑让我折腾了一下午。我启动了Master,然后启动了两个Node,结果它们就是连不上。后来发现,是ROS_MASTER_URI这个环境变量没设对。说白了,Node得知道Master在哪台机器上、哪个端口。跨机器通信时,这个变量特别容易出错。
避坑指南:我曾经在分布式机器人上调试,两台电脑的ROS_MASTER_URI都指向了同一台机器,但其中一台的防火墙没关,结果Node一直报“connection refused”。检查网络时,先ping一下,再telnet一下端口,别光看环境变量。
2.2 Topic通信机制:发布-订阅模式
Topic是ROS里最常用的通信方式。它的模式是发布-订阅。一个Node发布消息到某个话题,其他Node订阅这个话题,就能收到消息。一对多、多对多都行。
举个例子:激光雷达节点发布/scan话题,导航节点订阅/scan,就能拿到激光数据。如果再加一个可视化节点也订阅/scan,它也能收到。这就是一对多。
Topic通信的特点是异步、无反馈。发布者只管发,不管有没有人收。订阅者只管收,不管是谁发的。这种模式适合传感器数据、状态信息这种持续更新的数据。
# 发布者示例(Python)
import rospy
from std_msgs.msg import String
rospy.init_node('talker')
pub = rospy.Publisher('chatter', String, queue_size=10)
rate = rospy.Rate(10) # 10Hz
while not rospy.is_shutdown():
msg = "hello world %s" % rospy.get_time()
pub.publish(msg)
rate.sleep()
# 订阅者示例(Python)
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo("I heard: %s", data.data)
rospy.init_node('listener')
rospy.Subscriber('chatter', String, callback)
rospy.spin()
这里有个细节:queue_size参数。我刚开始写代码时,经常忘了设这个,或者设成0。queue_size=10表示缓冲区能存10条消息。如果订阅者处理得慢,新消息会覆盖旧消息。设成0的话,在ROS Noetic里会报warning,建议你设一个合理的值。
我的经验:对于高频传感器(比如激光雷达10Hz以上),queue_size设10-20就够了。对于低频数据(比如GPS 1Hz),设5-10就行。别设太大,否则内存会爆。
2.3 Service通信机制:请求-响应模式
Topic是“广播”,Service就是“点对点通话”。它的模式是请求-响应。客户端发一个请求,服务端处理完返回一个响应。整个过程是同步的,客户端会阻塞等待结果。
什么时候用Service?比如你让机器人去抓一个物体,你需要知道抓没抓到。或者你查询机器人的电池电量,需要立刻得到答案。这种有明确输入输出、需要确认结果的场景,用Service最合适。
# 服务端示例(Python)
import rospy
from beginner_tutorials.srv import AddTwoInts, AddTwoIntsResponse
def handle_add_two_ints(req):
result = req.a + req.b
rospy.loginfo("Returning [%s + %s = %s]" % (req.a, req.b, result))
return AddTwoIntsResponse(result)
rospy.init_node('add_two_ints_server')
s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
rospy.spin()
# 客户端示例(Python)
import rospy
from beginner_tutorials.srv import AddTwoInts
rospy.wait_for_service('add_two_ints')
try:
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
resp = add_two_ints(4, 5)
print("Result: %s" % resp.result)
except rospy.ServiceException as e:
print("Service call failed: %s" % e)
注意看,客户端里有个rospy.wait_for_service。这个函数会阻塞,直到服务端就绪。我在项目中遇到过一个问题:客户端启动比服务端快,结果一调用就报错。加上wait_for_service就稳了。
避坑指南:我曾经写了一个Service,客户端调用后一直没返回。查了半天,发现服务端里有个死循环,没处理完请求。Service的响应必须返回,否则客户端会一直等,直到超时。超时时间默认是30秒,可以设,但别滥用。
2.4 Action通信机制:带反馈的长任务
Action是Service的“升级版”。Service适合短任务,几毫秒就完事。但有些任务耗时很长,比如机器人导航到某个点,可能要几十秒。这时候用Service就不合适了——客户端会一直阻塞,而且你不知道进度。
Action引入了反馈机制。客户端发送一个目标,服务端可以持续反馈进度,最后返回结果。整个过程是异步的,客户端可以干别的事,同时监听反馈。
Action的核心概念有三个:Goal(目标)、Feedback(反馈)、Result(结果)。服务端收到Goal后,开始执行任务,定期发Feedback给客户端。任务完成后,发送Result。
# Action服务端示例(Python)
import rospy
import actionlib
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
def execute_cb(goal):
# 模拟导航过程
for i in range(10):
# 发送反馈
feedback = MoveBaseFeedback()
feedback.progress = i * 10
server.publish_feedback(feedback)
rospy.sleep(1)
# 返回结果
server.set_succeeded()
rospy.init_node('move_base_server')
server = actionlib.SimpleActionServer('move_base', MoveBaseAction, execute_cb, False)
server.start()
rospy.spin()
# Action客户端示例(Python)
import rospy
import actionlib
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
rospy.init_node('move_base_client')
client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
client.wait_for_server()
goal = MoveBaseGoal()
# 设置目标位置...
client.send_goal(goal, feedback_cb=feedback_cb)
client.wait_for_result()
def feedback_cb(feedback):
rospy.loginfo("Progress: %s%%" % feedback.progress)
Action的典型应用场景:导航、机械臂运动、路径规划。这些任务都需要长时间执行,而且用户希望看到进度。
总结一下三种通信机制:
| 机制 | 模式 | 是否同步 | 适用场景 |
|---|---|---|---|
| Topic | 发布-订阅 | 异步 | 传感器数据、状态广播 |
| Service | 请求-响应 | 同步 | 查询、短命令 |
| Action | 目标-反馈-结果 | 异步 | 长任务、需要进度反馈 |
嗯,到这里,ROS的三大通信机制就讲完了。你想想看,Topic像广播电台,Service像打电话,Action像发快递带物流跟踪。选哪个,取决于你的场景。
我个人建议:能用Topic就用Topic,因为它最简单、最灵活。需要确认结果时用Service。任务超过1秒、需要进度反馈时,果断上Action。别把Service当Action用,否则客户端卡死你。
我的习惯:写代码前,先画个通信图。哪些Node、哪些Topic、哪些Service、哪些Action,一目了然。别上来就写,否则后面改起来很痛苦。
下一章,咱们聊聊ROS的命名空间和参数服务器。这东西看着简单,但用不好会出大问题。我曾经因为命名空间没搞对,两个机器人互相抢数据,差点撞车。到时候细说。