想象一座智慧社区:保安在岗亭值守,物业在办公室处理报修,快递员穿梭送件,业主在楼里生活。每个人各司其职,从不打听彼此的地址和电话,却能井然有序地协作——靠的是楼道里一张张主题公告板:“快递到达”板、“社区活动”板、“安全巡逻”板。谁有新消息,就贴一张便条到对应的板上;谁关心这类消息,就定期来看一眼。贴的人不知道谁会来看,看的人不知道是谁贴的。 ROS2 的软件世界,就是这样一个社区。社区里的每一位成员叫节点(Node),公告板叫话题(Topic),便条叫消息(Message),而“公告板用多正式的送达方式、消息贴多久被撕掉”,由 QoS 服务质量参数决定。ROS1 时代,社区靠一个物业中心传达消息,中心一倒全社区瘫痪;ROS2 引入了 DDS 这套智慧通讯网络,成员之间自动发现、自主协作,不再有单点故障。 这篇文章带你逛一遍这座社区:认识节点、学会用话题收发消息、看懂 QoS 配置、理解 DDS 底层,最后动手用 Python 搭一座迷你公告板。

岗位关联

掌握 ROS2 节点与话题,是踏入机器人系统开发的第一道门槛。理解发布订阅与 QoS 模型,能帮你快速定位自动驾驶、多机协作等场景中的通信瓶颈,设计出高可靠、低延迟的机器人软件架构。

目录
  1. 可视化演示
  2. 节点:社区里各司其职的角色
  3. 话题:一块公告板连通全社区
  4. 消息:一张统一格式的便条
  5. QoS:公告板的星级服务标准
  6. DDS:社区背后的通讯网络
  7. 从ROS1到ROS2:从物业中心到业主自治
  8. 开发实践:立起第一块公告板
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示ROS2节点通信与话题的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

ROS2节点通信与话题:ROS节 ROS2节点概念与生命周期 话题通信与发布订阅模式 核心机制可视化 ROS2节点通信与话题整体架构与关键组件

节点:社区里各司其职的角色

小区里,保安的职责是巡逻盘问,物业的职责是处理报修,快递员的职责是派件——每个人从上班到下班,只专注做好自己那份事。他们不直接喊话协作,却让整个社区有序运转。在 ROS2 的世界里,这种“各司其职的最小工作单元”就叫做节点(Node)。

一个节点就是一段运行中的程序,它把自己的任务封装好:“读取激光雷达数据”是一个节点,“根据数据规划路径”是另一个节点。一辆机器人小车,往往同时跑着几十个节点:传感器驱动节点、里程计节点、导航节点、控制节点…… 它们分工明确,又通过后面的“公告板”保持协作。

在工程上,用 rclpy 或 rclcpp 创建节点对象,节点有自己的生命周期:创建、执行回调、销毁。所有节点和话题连接在一起,就构成了一张有向计算图(Computation Graph),用 rqt_graph 可以直观看到——哪个节点发布什么、订阅什么,一目了然。图中每个圆圈是一个节点,每条边是一条话题连接。

就像社区的组织架构图:节点是画在纸上的一个个岗位,话题是岗位之间传递消息的通道。你不需要知道保安的工牌号,只需要知道“找保安岗”。

G=(N,E)\displaystyle G = (N, E)
一个机器人系统可以被抽象成一张计算图:节点是图的顶点,话题连接是图的边。
  1. N 节点集合$N$ 表示系统中全部节点的集合,对应社区里每一位在岗成员。
    N={n1,n2,…,nm}N = \{n_1, n_2, \ldots, n_m\}
  2. E 边集合$E$ 表示节点之间话题连接的集合,每条边代表一个“谁订阅了谁”的通信关系。
    E⊆N×NE \subseteq N \times N
  3. G 计算图$G$ 是整张计算图,它刻画了机器人系统里所有节点如何通过话题协同工作。
    G=(N,E)G = (N, E)

话题:一块公告板连通全社区

社区楼道里钉着一块“快递到达公告板”。快递员到达后,往板上贴一张便条:“3栋502有包裹”,贴完就走,绝不原地等业主来取。业主有空下楼时看一眼板子,看到便条就去取件。贴的人不知道谁会看,看的人不知道谁贴的——这就是发布订阅。

ROS2 的话题(Topic)就是这样的具名公告板。发布者(Publisher)调用 publish() 把消息“贴”到话题上,订阅者(Subscriber)预先登记自己想看的话题,话题一旦来了新消息,系统自动把消息送到订阅者的回调函数里。整个过程是异步的:发布者不等订阅者回应,订阅者也不阻塞发布者。

话题是“多对多”的:多个发布者可以往同一块板子上贴便条,多个订阅者也可以同时关注同一块板子。每贴一条消息,有多少订阅者,系统就会复制多少份投递出去。消息的投递总量因此等于发布次数乘以订阅者数量——这是估算系统负载的第一个公式。

回到社区:一块快递公告板,快递员放一件包裹,十位业主各自来取,一次放件就被“消费”十次。消息本身不会因为看得人多了就变大,但系统的分发工作量会成倍增加。

M=Nsub×P\displaystyle M = N_{\text{sub}} \times P
发布一次消息,会在每个订阅者那里各投递一份;订阅者越多,系统的分发工作量就越大。
  1. P 发布总次数$P$ 表示发布者向话题投递消息的总次数,即贴了多少张便条。
    P∈NP \in \mathbb{N}
  2. N_sub 订阅者数量$N_{\text{sub}}$ 表示订阅该话题的节点个数,即关注这块公告板的人数。
    Nsub=∣subscribers∣N_{\text{sub}} = |\text{subscribers}|
  3. M 消息总量$M$ 表示在系统中实际产生的消息投递总量,由订阅者数量乘以发布次数得出。
    M=Nsub×PM = N_{\text{sub}} \times P

消息:一张统一格式的便条

便条如果格式随意,有人写“速来”,有人写“下午三点东门”,沟通必然混乱。社区因此统一了便条模板:顶部是时间戳和位置栏,下面是内容栏。所有成员都按模板填写,任何岗位都能读懂。

消息(Message)就是 ROS2 的“便条模板”。它是强类型定义的结构化数据,支持 int32、float32、string 及嵌套消息等字段。ROS2 内置了丰富的标准消息:std_msgs 提供基础类型和 Header 头,sensor_msgs 提供传感器数据(LaserScan 激光扫描、Imu 惯性测量),geometry_msgs 提供位姿与速度(Twist 速度指令),开箱即用。

如果标准消息不够用,还可以写一个 .msg 文件定义自定义消息,声明字段名和类型,编译后自动生成对应语言的类。发布时,消息被序列化成字节流(ROS2 默认采用 CDR 格式,这是 DDS 的标准序列化协议);订阅端收到字节流后反序列化还原成对象。因为序列化格式统一,C++ 节点发布的消息,Python 节点也能读懂——跨语言通信就这么实现。一条消息在结构上可以看作“头部 + 若干业务字段”。

便条模板保证了“快递员写的条,物业也能念”;消息类型则保证了“C++ 贴的条,Python 也能读”。模板一致,沟通才高效。

m=(h,f1,f2,…,fn)\displaystyle m = (h, f_1, f_2, \ldots, f_n)
一条消息由消息头和时间戳、坐标系等元信息,加上若干业务数据字段组合而成。
  1. h 消息头$h$ 是消息头部,携带时间戳和坐标系等元信息,相当于便条顶部的“几点、在哪”。
    h=(stamp,frame_id)h = (\text{stamp}, \text{frame\_id})
  2. f_i 业务字段$f_1, f_2, \ldots, f_n$ 是消息携带的业务数据,每个字段都有明确的类型。
    fi∈{int32,float32,string,…}f_i \in \{\text{int32}, \text{float32}, \text{string}, \ldots\}
  3. m 完整消息一条消息 $m$ 由头部和若干字段组成,发布端和订阅端按同一模板解析。
    m=(h,f1,f2,…,fn)m = (h, f_1, f_2, \ldots, f_n)

QoS:公告板的星级服务标准

公告板也分星级。普通板:便条直接贴墙上,风一吹就没了,晚来的业主看不到(volatile);VIP 板:物业把便条登记在册,新来的业主也能查询历史(transient_local)。送信方式也有讲究:贵重小件用“挂号信”,丢了必须重发(reliable);垃圾袋这种不值钱的,平信一投了之(best_effort)。

在 ROS2 里,这套“公告板服务标准”就是 QoS(Quality of Service,服务质量)。常用的几组参数:Reliability 可靠性策略——reliable 保证送达(可重传),best_effort 尽力而为(不重传);Durability 持久性策略——volatile 不保留历史,transient_local 为后来的订阅者保留最近历史;History 历史策略——Keep Last 只留最近 N 条。

怎么选?控制指令、状态上报这类“丢一条就出事”的消息,用 reliable;激光雷达点云、摄像头图像这类高频海量、且人眼根本看不出丢了一帧的数据,用 best_effort——既省带宽又低延迟。reliable 之所以“可靠”,靠的是失败重传:假设单次发送成功概率为 $\rho$,每次失败后重传,重传 $k$ 次后送达率会指数级逼近 100%。

用挂号和平信来记:挂号信虽然慢一点,但重要文件必须签收;平信快,丢了也不心疼。给公告板配 QoS,本质就是在“必达”和“快达”之间做权衡。如果你订了“平信服务”,却要求“挂号保障”,系统会直接告诉你:QoS 不匹配。

R=1−(1−ρ)k\displaystyle R = 1 - (1 - \rho)^k
重传次数越多,消息最终送达的概率越高;这是可靠传输的本质:用重试换取“必达”。
  1. ρ 单次成功概率$\rho$ 表示单次发送消息成功的概率,取值在 0 到 1 之间。
    ρ∈(0,1)\rho \in (0, 1)
  2. k 重传次数$k$ 表示失败后的重传次数,reliable 服务会持续重试直至成功。
    k∈Nk \in \mathbb{N}
  3. R 最终送达率$(1-\rho)^k$ 是连续失败 $k$ 次的概率,用 1 减去它就得到最终送达概率。
    R=1−(1−ρ)kR = 1 - (1 - \rho)^k

DDS:社区背后的通讯网络

社区里没有大喇叭。新搬来的住户怎么被大家认识?很简单:各家各户都接入了社区通讯网,新人入住时自动广播一句“我是新住户,住在 X 栋,我关心社区活动”;在线的住户听到后,就把新人加进通讯录。整个过程不需要物业主持——这就是“自动发现”。

ROS2 的底层通讯网就是 DDS(Data Distribution Service,数据分发服务)。它是 OMG 组织制定的工业标准中间件,以“数据为中心”实现发布订阅。ROS2 通过 RMW(ROS Middleware,中间件抽象层) 接口对接具体厂商的 DDS 实现,如 Fast DDS、Cyclone DDS 等。

DDS 的发现机制分两步:第一步 SPDP,新节点周期性广播“我上线了”,让其他节点发现自己;第二步 SEDP,节点之间进一步交换“我发布哪些话题、订阅哪些话题”的信息,完成话题匹配。整个过程全对等、无中心,任何一个节点离线都不影响其他人通信。社区里 N 个成员要互相认识,每对成员握手一次,总共需要 N(N-1)/2 次。

正因如此,ROS2 里“加一台机器人”就像“社区搬来新住户”——插上网线、启动程序,它自动被全网发现,其他节点立刻能与它通信,无需任何手工配置。

C=N(N−1)2\displaystyle C = \frac{N(N-1)}{2}
全网成员两两握手建立发现关系,握手次数随节点数平方增长,这就是去中心化要付出的“认识成本”。
  1. N 节点总数$N$ 表示系统里的节点个数,对应社区成员数量。
    N=∣nodes∣N = |\text{nodes}|
  2. N(N-1) 双向关系每个节点要与其他 $N-1$ 个节点握手,全部关系数为 $N(N-1)$。
    N(N−1)N(N-1)
  3. C 去重后总数A 认识 B 与 B 认识 A 是同一次握手,除以 2 去掉重复计数。
    C=N(N−1)2C = \frac{N(N-1)}{2}

从ROS1到ROS2:从物业中心到业主自治

ROS1 时代的社区,所有消息都要经过“物业中心”(roscore 主节点)转达。业主想借工具,得先打电话问物业“谁家有”,物业再联系对方;物业一忙线,整个社区就瘫痪了。这个单点故障在机器人里很致命——主节点一挂,所有通信全部中断,机器人当场“失联”。

ROS2 果断换掉了这套架构:底层不再用 ROS1 自定义的 TCPROS 协议,而是采用工业标准 DDS。消息不再经过任何中心,发布者直接和订阅者建立数据通路。这带来三大改进:一是去中心化,没有单点故障;二是实时性,DDS 支持共享内存、零拷贝等低延迟传输,满足机器人毫秒级响应需求;三是安全性,DDS 自带身份认证与加密(DDS Security),还能运行在 Linux、Windows、RTOS 等不同平台上。

一次话题通信的端到端延迟,可以分解为三段:发布节点的序列化与发送开销、网络传输开销、订阅节点的接收与反序列化开销。ROS1 的每条消息都要经过 master 中转和多次拷贝,延迟高且不稳定;ROS2 使用 DDS 后,同一进程内的节点甚至可以走共享内存,消息只在内存里“递”一次,延迟从毫秒级降到微秒级。

从“有事找物业”到“邻里直接互助”,ROS2 把决定权还给了每一个节点。物业中心退休了,社区的效率反而更高了。

Te2e=Tpublish+Ttransport+Tsubscribe\displaystyle T_{\text{e2e}} = T_{\text{publish}} + T_{\text{transport}} + T_{\text{subscribe}}
端到端延迟是三个环节的累加:发布端处理、网络传输、订阅端处理;ROS2 重点压缩了中间的传输环节。
  1. T_publish 发布端开销$T_{\text{publish}}$ 是消息在发布节点内的序列化与发送处理时间。
    Tpublish=Tserialize+TwriteT_{\text{publish}} = T_{\text{serialize}} + T_{\text{write}}
  2. T_transport 传输耗时$T_{\text{transport}}$ 是消息在网络或共享内存中传输的时间,ROS2 的零拷贝在此大幅省时。
    Ttransport=TwireT_{\text{transport}} = T_{\text{wire}}
  3. T_subscribe 订阅端开销$T_{\text{subscribe}}$ 是订阅节点接收、反序列化的时间。
    Tsubscribe=Tread+TdeserializeT_{\text{subscribe}} = T_{\text{read}} + T_{\text{deserialize}}
  4. T_e2e 端到端延迟三个环节相加即是用户感知的完整通信延迟,每个环节都是可优化的靶点。
    Te2e=Tpublish+Ttransport+TsubscribeT_{\text{e2e}} = T_{\text{publish}} + T_{\text{transport}} + T_{\text{subscribe}}

开发实践:立起第一块公告板

理论逛完了,动手在社区里立第一块公告板吧。任务很简单:一个“车速发布者”节点,每隔一秒往“车速话题”上贴一条消息;两个“车速查看者”节点(显示屏和记录仪),订阅同一话题,各收各的。

用 Python(rclpy)开发:发布者节点里,create_publisher() 创建发布器,再创建定时器(Timer),回调里调用 publish() 发布消息;订阅者节点里,create_subscription() 注册回调,话题每来一条新消息,回调函数就被自动调用一次。写完后用 ros2 run 启动节点,消息就哗啦啦地流动起来了。

调试时,命令行是最好的朋友:ros2 node list 看所有节点,ros2 topic list 看所有话题,ros2 topic echo /车速话题 实时打印消息流,ros2 topic hz /车速话题 统计发布频率,rqt_graph 可视化整张计算图。上线前还可以用带宽公式粗算流量:消息字节数、发布频率、订阅者数量,三者相乘就是该话题每秒消耗的带宽,提前预判网络压力。

搭完你会神奇地发现:发布者代码和订阅者代码里,互相都没有出现对方的任何信息——没有 IP、没有节点名、没有回调地址。它们只认识“车速话题”这块公告板。这就是 ROS2 通信设计的灵魂:解耦。

B=Smsg×f×Nsub\displaystyle B = S_{\text{msg}} \times f \times N_{\text{sub}}
话题每秒消耗的带宽 = 单条消息大小 × 发布频率 × 订阅者数量,是评估通信负载的核心公式。
  1. S_msg 消息体积$S_{\text{msg}}$ 是单条消息序列化后的字节数,便条写得越长,占用空间越大。
    Smsg∈NS_{\text{msg}} \in \mathbb{N}
  2. f 发布频率$f$ 是发布者每秒发布消息的次数,频率越高,公告板更新越勤。
    f=1Δtf = \frac{1}{\Delta t}
  3. N_sub 订阅者数量$N_{\text{sub}}$ 是订阅该话题的节点数,每多一个订阅者,消息就要多复制一份。
    Nsub∈NN_{\text{sub}} \in \mathbb{N}
  4. B 话题带宽$B$ 是话题每秒消耗的总带宽,三项相乘即可快速估算网络负载。
    B=Smsg×f×NsubB = S_{\text{msg}} \times f \times N_{\text{sub}}

AI 应用场景

自动驾驶多传感器融合

阶段:数据接入与感知融合

解决的问题:传统做法是传感器通过私有协议点对点直连,每加一个传感器就要改一堆对接代码。用 ROS2 后,每个传感器(激光雷达、摄像头、IMU)都是一个发布节点,向 /points、/image、/imu 话题发布数据;融合节点订阅多个话题,按时间戳对齐做目标检测。雷达点云量大用 best_effort 保证实时性,控制指令用 reliable 保证不丢,这正是 QoS 思想在工程中的落地。

具体案例:某 L4 自动驾驶团队用 ROS2 重构感知栈:速腾激光雷达节点以 10Hz 发布点云,摄像头节点以 30Hz 发布图像,融合节点订阅四个话题做目标检测。他们用 ros2 topic hz 监控各话题帧率,很快定位到相机节点 CPU 占用过高导致的丢帧问题,优化后检测延迟降低 40%。

机器人远程运维监控

阶段:状态上报与远程诊断

解决的问题:传统中控平台主动轮询每台机器人,机器人一多请求排队,状态刷新慢。改用 ROS2 话题后,机器人端各节点把关节角、电池电量、温度发布到 /joint_states、/battery 等话题,中控大屏和手机 App 订阅即可实时拿到状态;配上 transient_local 持久性策略,晚接入的客户端也能立即看到当前状态,不用等下一次上报。

具体案例:某工厂 50 台 AGV 部署 ROS2 监控系统,运维人员打开大屏即可看到所有 AGV 的实时位姿与电量。新增一台 AGV 接入网络后,DDS 自动发现让它在 2 秒内自动出现在监控画面上,全程无需修改中控代码。

多机协作仓储调度

阶段:多智能体协同

解决的问题:传统调度系统需要维护每台机器人的地址,机器人之间无法直接沟通。ROS2 中多台 AGV 各自作为节点,通过共享话题(如 /robot_pose、/task_status)互发位置与任务状态;调度算法订阅全局状态做路径规划,DDS 自动发现让新机器人即插即用,系统扩容只需加节点,无需改代码。

具体案例:某智慧仓库 20 台 AGV 通过 ROS2 话题共享位置,当两台 AGV 预测轨迹冲突时,调度节点根据话题上的实时位置数据下发避让指令。上线后碰撞误报率降低 60%,AGV 调度响应时间从秒级缩短到毫秒级。

Python 演示

python64 行
"""ROS2 节点与话题的微型模拟:社区公告板版.""" import time  class Broker:    """话题总管:模拟 DDS 的数据分发中心."""     def __init__(self):        self._subs_map = {}     def subscribe(self, topic, callback):        self._subs_map.setdefault(topic, []).append(callback)     def publish(self, topic, msg):        for callback in self._subs_map.get(topic, []):            callback(msg)  broker = Broker()  class Node:    """节点:社区里的一位成员,只专注自己的职责."""     def __init__(self, name):        self.name = name     def create_publisher(self, topic):        return Publisher(topic)     def create_subscription(self, topic, callback):        broker.subscribe(topic, callback)  class Publisher:    """发布者:往话题公告板上贴便条."""     def __init__(self, topic):        self.topic = topic     def publish(self, msg):        broker.publish(self.topic, msg)  def main():    log = []     def on_speed(msg):        log.append(msg)     speed_node = Node("speed_node")    pub = speed_node.create_publisher("speed")    Node("display_node").create_subscription("speed", on_speed)    Node("recorder_node").create_subscription("speed", on_speed)    for i in range(3):        pub.publish({"speed": 0.5 + i, "stamp": i})        time.sleep(0.05)    print("两个订阅者共收到", len(log), "条消息")    print("最后一条消息:", log[-1])  if __name__ == "__main__":    main()
目的:用纯 Python 模拟 ROS2 的节点、发布者、订阅者和话题,展示一条消息被多个订阅者接收的“一对多”分发过程。
  1. self._subs_map.setdefault(topic, []).append(callback)注册订阅:第一次遇到该话题先建空列表,再把回调加进去
  2. for callback in self._subs_map.get(topic, [])取该话题的全部订阅回调,逐个调用实现广播
  3. broker.publish(self.topic, msg)发布者把消息交给话题总管,自己不关心谁在听
  4. Node("display_node").create_subscription("speed", on_speed)创建订阅节点并注册回调,模拟 ROS2 的 create_subscription
  5. Node("recorder_node").create_subscription("speed", on_speed)第二个订阅者,演示同一条消息的多份分发
  6. pub.publish({"speed": 0.5 + i, "stamp": i})发布带序号的消息,对应 publish() 调用
  7. log.append(msg)订阅回调里记录收到的消息,证明发布者和订阅者确实互不感知

常见误区

误区我一直以为一个 ROS2“节点”就是一台机器人,机器人上有几个传感器就有几个节点,节点数越多机器人越高级。后来发现自己的理解完全对不上代码里的概念。

正解节点是“执行某一特定任务的最小计算单元”,是运行中的一段程序,而不是硬件本体。一台机器人上往往跑着几十个节点:摄像头驱动是一个节点,导航算法是一个节点,电机控制又是一个节点。就像社区里一个岗亭有多个职责角色一样,一台机器人的“躯体”里住着许多各司其职的节点。

为何常见:"node"这个词在直觉上容易让人联想到网络设备或硬件节点,加上机器人又是实体设备,概念边界就容易被混淆。

误区我总觉得要订阅一个话题,就得先找到发布者节点的 IP 和端口,像网页请求那样指定服务器地址。不然消息从哪来呢?

正解话题通信是发布-订阅模式,双方完全解耦。发布者不认识订阅者,订阅者也不认识发布者,连接和匹配由 DDS 底层自动完成——双方只认话题名。就像你只看公告板,根本不需要知道便条是谁贴的;物业(DDS)会自动把消息送到关注这块板的人手里。

为何常见:我们习惯了客户端-服务器模式,总觉得通信必须有明确的双方地址;而发布订阅的精髓正是“匿名”和“解耦”,这需要转换思维。

误区QoS 里的 `reliable` 听起来比 `best_effort` 高级多了,那我所有话题都配 `reliable`,消息一个不丢,肯定最稳妥。

正解reliable 靠重传保证送达,频发重传会带来额外时延和带宽占用。对激光雷达点云、摄像头图像这类高频大流量数据,丢一两帧毫无感知,用 reliable 反而会造成数据积压、实时性变差。就像为了确保“必达”让快递员反复敲门,生鲜水果早就烂在路上了。正确做法是:重要控制指令用 reliable,海量感知数据用 best_effort。

为何常见:“可靠”这个词天然带着正面色彩,初学者容易忽略它背后的代价——实时性与可靠性之间的权衡。

误区我以为发布到话题上的消息会像聊天记录一样一直存着,晚来的订阅者打开就能看到之前所有历史消息。

正解默认的 volatile 策略下,消息发完即走,只有当时在线的订阅者能收到;后来订阅的节点看不到任何历史。要让后来的订阅者补收历史消息,需要配置 transient_local 持久性策略,配合 Keep Last 指定保留条数。就像普通公告板上的通知过时不候,除非专门设置一块“历史通知栏”,否则撕掉就没了。

为何常见:数据库、聊天软件都有“历史记录”的常识,迁移到 ROS2 话题时不自觉套用了存储模型的直觉,忽略了话题本质是“实时流”而非“存储”。

误区ROS2 不就是 ROS1 修了几个 bug、换了个版本号的加强版吗?API 差不多,底层应该也差不多吧。

正解ROS2 在底层做了根本性重构:通信中间件从 ROS1 自定义的 TCPROS 换成工业标准 DDS,架构从依赖 roscore 中心主节点的星型结构,换成去中心化、自动发现的网状结构。这带来了实时性、安全性、跨平台能力的全面提升。就像社区从“所有消息经物业中心转达”(物业一倒全瘫)升级为“邻里直接互助”(任何一户离线都不影响别人)。

为何常见:名字只差一个版本号,API 又保持相似,很容易让人觉得只是小版本迭代,而忽略了中间件级别的架构变革。

面试题

Q1. 关于ROS2中的“节点(Node)”,最准确的理解是?

单选题 · easy

  • A.一台完整的机器人硬件
  • B.一个独立的最小计算单元← 正确答案
  • C.机器人内置的操作系统
  • D.保存消息的数据库文件

答案:B。节点是执行单一职责的最小可执行单元,一辆机器人通常跑几十个节点协同工作,就像社区里保安、物业、快递员各司其职,而不是指整台机器人硬件。

Q2. 话题(Topic)采用的核心通信模式是?

单选题 · easy

  • A.点对点私聊直连模式
  • B.发布订阅的广播模式← 正确答案
  • C.客户端服务器的请求模式
  • D.全节点共享的同步模式

答案:B。话题是发布-订阅模式:发布者把消息贴到公告板(话题)上,订阅者自行查看。双方不直接联系,也不需要同步等待回应,这就是公告板协作的本质。

Q3. 发布者向话题发布一条消息后,谁能够收到?

单选题 · easy

  • A.系统中的全部节点
  • B.发布者所在的节点
  • C.当时订阅该话题的节点← 正确答案
  • D.与发布者同进程的节点

答案:C。只有当下订阅了该话题的节点才会收到消息;没订阅的节点即使在同一台机器人上也不会收到。就像没关注公告板的人看不到上面的便条。

Q4. 在ROS2话题的QoS配置中,depth参数的作用是?

单选题 · mid

  • A.限制消息的最大字节数
  • B.指定历史队列保留的消息条数← 正确答案
  • C.决定发布者的发送频率上限
  • D.控制订阅者线程的数量上限

答案:B。depth 是“历史深度”:在 Keep Last 策略下队列最多保留最近 N 条消息,超出部分的旧消息会被丢弃。它配合 volatile/transient_local 决定历史如何保存,与字节数、频率、线程数无关。

Q5. 激光雷达点云数据量大、实时性要求高,最适合配置哪种QoS?

单选题 · mid

  • A.reliable 并配合较大的深度
  • B.best_effort 并配合较小的深度← 正确答案
  • C.transient_local 并配合较大的深度
  • D.完全不配置任何 QoS 参数

答案:B。点云每帧数据量大且丢一帧人眼无感,用 best_effort 可避免重传造成的延迟积压;队列深度小能保证订阅者看到最新一帧,而不是排队读取旧数据。这正体现了“平信给不值钱的海量数据、挂号信给重要指令”的权衡思想。

Q6. ROS2 相比 ROS1 在通信架构上最本质的改进是?

单选题 · mid

  • A.新增一个全局中心主节点
  • B.用 DDS 替代自定义 TCPROS← 正确答案
  • C.将消息格式改为纯文本 JSON
  • D.取消了节点的自动发现机制

答案:B。ROS1 用 roscore 加 TCPROS 实现中心化通信;ROS2 换用标准 DDS 中间件,去中心化并支持自动发现,消除了“主节点挂掉全系统瘫痪”的单点故障。好比社区从“有事找物业”升级为“邻里直接互助”。

Q7. 关于ROS2的消息与自定义消息,下列说法正确的是?

多选题 · mid

  • A.消息类型在 .msg 文件中定义← 正确答案
  • B.标准消息不能用户扩展使用
  • C.自定义消息编译后生成语言类← 正确答案
  • D.消息必须以 JSON 格式传输

答案:AC。.msg 文件声明字段名和类型,编译后自动生成 C++/Python 的类;标准消息可以直接使用,也可以组合进自定义消息。传输走的是 CDR 二进制序列化而不是 JSON,这正是跨语言互通的基础。

Q8. 发布者配置 best_effort、订阅者配置 reliable 时,会发生什么?

单选题 · hard

  • A.双方自动升级为完全可靠传输
  • B.话题连接失败或提示 QoS 不兼容← 正确答案
  • C.系统自动降级发布改为可靠
  • D.消息会绕过 QoS 强行送达

答案:B。订阅者要求“必达”,发布者只承诺“尽力而为”,保障能力不匹配。DDS 在发现阶段就会判定 QoS 不兼容,连接无法建立(或提示后降级),不会自动升级。这就像订了平信却要求挂号保障——服务等级对不上。

Q9. 以下哪些属于ROS2话题通信的特点?

多选题 · mid

  • A.发布者与订阅者完全解耦← 正确答案
  • B.双方需要同步等待对方回应
  • C.支持一对多的消息分发← 正确答案
  • D.通信依赖中心主节点转发

答案:AC。话题是异步的发布订阅:双方互不知道对方存在(解耦),一条消息可被多个订阅者各自接收(一对多)。它不需要同步等待回应,也不依赖中心主节点——那是 ROS1 的架构。

Q10. 为了降低ROS2话题通信的端到端延迟,可以采取哪些措施?

多选题 · hard

  • A.减少消息序列化的拷贝次数← 正确答案
  • B.用 best_effort 替换 reliable← 正确答案
  • C.增大历史队列的 depth 参数
  • D.使用零拷贝共享内存传输← 正确答案

答案:ABD。减少拷贝、零拷贝共享内存直接压缩传输环节的开销;best_effort 免去重传等待,适合海量感知数据。而增大 depth 只是增加缓冲,消息排队反而可能让“最新数据”到手更慢,无助于降低延迟。