ros2 基本概念 c++ 解释

ros2 基本概念 c++ 解释 /* * ROS2 面试核心概念全覆盖示例代码 * 覆盖90%以上ROS2高频面试考点每个模块附带【面试考点】详细注释 * 包含节点/回调/执行器/回调组、话题/服务/Action三大通信、DDS与QoS、生命周期节点、参数机制 * 编译环境ROS2 Humble/Jazzy 全版本通用 */ // ############################## 头文件区对应核心模块############################## // ROS2 核心基础头文件包含节点、日志、基础API #include rclcpp/rclcpp.hpp // 标准消息类型字符串消息用于话题通信 #include std_msgs/msg/string.hpp // 标准服务类型加法服务用于服务通信 #include example_interfaces/srv/add_two_ints.hpp // 标准动作类型斐波那契数列用于Action通信 #include example_interfaces/action/fibonacci.hpp // 三种执行器头文件【面试必考三种执行器区别与适用场景】 #include rclcpp/executors/single_threaded_executor.hpp #include rclcpp/executors/multi_threaded_executor.hpp #include rclcpp/executors/static_single_threaded_executor.hpp // 回调组头文件【面试必考多线程调度核心和执行器的配合逻辑】 #include rclcpp/callback_group.hpp // 生命周期节点头文件【面试必考状态机、和普通节点的区别】 #include rclcpp_lifecycle/lifecycle_node.hpp #include rclcpp_lifecycle/lifecycle_publisher.hpp // Action 相关头文件【面试必考Action和服务/话题的区别】 #include rclcpp_action/rclcpp_action.hpp // QoS 服务质量头文件【面试必考DDS核心能力ROS2可靠性保障】 #include rclcpp/qos.hpp using std::placeholders::_1; using std::placeholders::_2; using std::placeholders::_3; // ############################## 模块1综合基础功能节点 ############################## // 【面试考点1节点的本质】 // 节点是ROS2的最小功能单元本质是「回调任务通信接口的容器」 // 节点本身不执行任何代码所有回调必须由执行器(Executor)调度才能运行 // 一个进程可以创建多个节点一个执行器可以挂载多个节点 class BasicCoreNode : public rclcpp::Node { public: // 节点构造函数所有初始化、接口创建都在这里完成 BasicCoreNode(const std::string node_name) : Node(node_name) { // 1. 参数机制面试考点 // 【面试考点ROS2参数和ROS1的区别】 // ROS1中心化参数服务器所有节点共享存在单点故障 // ROS2去中心化架构每个节点自己维护参数没有全局参数服务器支持动态修改 // 声明一个参数参数名timer_period_ms默认值500ms this-declare_parameterint(timer_period_ms, 500); // 读取参数值 int timer_period this-get_parameter(timer_period_ms).as_int(); RCLCPP_INFO(this-get_logger(), 【参数初始化】定时器周期%dms, timer_period); // 2. 回调组面试核心考点 // 【面试考点回调组的作用和执行器的关系】 // 回调组是执行器调度的最小单元用来对回调进行分组管理决定回调是否可以并行执行 // 两种核心回调组 // 1. MutuallyExclusive互斥组组内的回调只能串行执行同一时间只跑一个节点默认创建的就是互斥组 // 2. Reentrant可重入组组内的回调可以并行执行多线程环境下同时运行 // 注意即使是多线程执行器同一个互斥组里的回调也绝对不会并行 // 创建两个独立的互斥回调组订阅回调和服务回调分开调度互不阻塞 callback_group_sub_ this-create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); callback_group_srv_ this-create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); // 3. 话题Topic 通信面试必考 // 【面试考点话题通信模式与特点】 // 模式发布-订阅Pub/Sub异步通信 // 特点一对多一个发布者可以对应N个订阅者、无应答确认、低延迟 // 适用场景高频连续数据流比如传感器数据、机器人状态、运动指令下发 rclcpp::QoS topic_qos rclcpp::QoS(10); // QoS队列深度10 // 3.1 创建话题发布者 // 模板参数消息类型入参话题名、QoS策略 publisher_ this-create_publisherstd_msgs::msg::String(chatter_topic, topic_qos); // 3.2 创建话题订阅者绑定到订阅回调组 // 回调函数收到消息时自动触发属于最典型的回调函数 subscriber_ this-create_subscriptionstd_msgs::msg::String( chatter_topic, topic_qos, std::bind(BasicCoreNode::topic_callback, this, _1), rclcpp::SubscriptionOptions(), callback_group_sub_ // 指定该订阅的回调归属的回调组 ); // 4. 服务Service 通信面试必考 // 【面试考点服务通信模式和话题的核心区别】 // 模式请求-响应Req/Rep同步通信 // 特点一对一一个请求对应一个响应、有确定返回结果、客户端默认阻塞等待 // 适用场景低频、有明确结果的短任务比如触发校准、查询状态、参数设置 add_srv_ this-create_serviceexample_interfaces::srv::AddTwoInts( add_two_ints, std::bind(BasicCoreNode::service_callback, this, _1, _2), rmw_qos_profile_services_default, callback_group_srv_ ); // 5. 定时器Timer // 定时器回调按固定周期触发也是最常用的回调类型 // 常用于周期发布数据、周期检查设备状态 timer_ this-create_wall_timer( std::chrono::milliseconds(timer_period), std::bind(BasicCoreNode::timer_callback, this) ); RCLCPP_INFO(this-get_logger(), 基础节点[%s]创建完成所有回调任务已装入容器等待执行器调度, node_name.c_str()); } // 服务客户端示例函数主动发起服务请求 void call_add_service(int a, int b) { // 创建服务客户端 auto client this-create_clientexample_interfaces::srv::AddTwoInts(add_two_ints); // 等待服务端上线 while (!client-wait_for_service(std::chrono::seconds(1))) { RCLCPP_INFO(this-get_logger(), 等待服务端上线...); } // 构造请求体 auto request std::make_sharedexample_interfaces::srv::AddTwoInts::Request(); request-a a; request-b b; // 异步发送请求绑定响应回调 client-async_send_request(request, std::bind(BasicCoreNode::service_response_callback, this, _1)); } private: // 各类回调函数 // 【面试考点回调函数的本质】 // 开发者只定义函数、绑定到对应事件不主动调用由ROS2执行器在事件触发时自动调用 // 所有回调的执行完全依赖执行器的spin()循环没有执行器驱动回调永远不会运行 // 1. 话题订阅回调收到话题消息时自动触发 void topic_callback(const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(this-get_logger(), 【话题回调触发】收到消息: %s, msg-data.c_str()); } // 2. 服务回调收到服务请求时自动触发处理后返回响应 void service_callback( const std::shared_ptrexample_interfaces::srv::AddTwoInts::Request request, std::shared_ptrexample_interfaces::srv::AddTwoInts::Response response ) { response-sum request-a request-b; RCLCPP_INFO(this-get_logger(), 【服务回调触发】%d %d %d, request-a, request-b, response-sum); } // 3. 服务响应回调客户端收到服务端响应时自动触发 void service_response_callback(rclcpp::Clientexample_interfaces::srv::AddTwoInts::SharedFuture future) { auto response future.get(); RCLCPP_INFO(this-get_logger(), 【服务响应回调】收到求和结果: %ld, response-sum); } // 4. 定时器回调到设定周期自动触发 void timer_callback() { auto msg std_msgs::msg::String(); msg.data 周期发布的话题消息; publisher_-publish(msg); RCLCPP_INFO(this-get_logger(), 【定时器回调触发】发布话题消息); } // 节点成员容器里的功能条目 rclcpp::Publisherstd_msgs::msg::String::SharedPtr publisher_; rclcpp::Subscriptionstd_msgs::msg::String::SharedPtr subscriber_; rclcpp::Serviceexample_interfaces::srv::AddTwoInts::SharedPtr add_srv_; rclcpp::TimerBase::SharedPtr timer_; rclcpp::CallbackGroup::SharedPtr callback_group_sub_; rclcpp::CallbackGroup::SharedPtr callback_group_srv_; }; // ############################## 模块2Action 动作通信节点 ############################## // 【面试必考Action是什么和服务/话题的核心区别】 // 1. 本质基于话题服务实现的高级通信机制专门针对长时任务设计 // 2. 模式目标-反馈-结果异步可抢占 // 3. 核心组成 // - Goal 目标客户端发送任务目标 // - Feedback 反馈服务端周期返回任务执行进度 // - Result 结果任务完成后返回最终结果 // - Cancel 取消客户端可以中途取消/抢占任务 // 4. 和服务的核心区别 // - 服务短平快任务同步等待没有进度反馈不能中途取消 // - Action长时任务异步执行有实时进度反馈支持取消/抢占 // 5. 适用场景导航移动、机械臂运动、拍照序列等耗时长、需要进度感知的任务 class ActionServerNode : public rclcpp::Node { public: // Action 类型别名简化代码 using Fibonacci example_interfaces::action::Fibonacci; using GoalHandleFibonacci rclcpp_action::ServerGoalHandleFibonacci; ActionServerNode(const std::string node_name) : Node(node_name) { // 创建Action服务端绑定三个核心回调 action_server_ rclcpp_action::create_serverFibonacci( this, fibonacci_action, // Action名称 // 目标接收回调收到新目标时触发决定接受还是拒绝 std::bind(ActionServerNode::handle_goal, this, _1, _2), // 取消请求回调收到取消指令时触发决定是否允许取消 std::bind(ActionServerNode::handle_cancel, this, _1), // 目标接受回调目标被接受后执行任务的主逻辑 std::bind(ActionServerNode::handle_accepted, this, _1) ); RCLCPP_INFO(this-get_logger(), Action服务端节点[%s]创建完成, node_name.c_str()); } private: // 1. 目标处理回调收到新目标返回接受/拒绝 rclcpp_action::GoalResponse handle_goal( const rclcpp_action::GoalUUID uuid, std::shared_ptrconst Fibonacci::Goal goal ) { RCLCPP_INFO(this-get_logger(), 【Action回调】收到新目标阶数%d, goal-order); (void)uuid; // 阶数大于100就拒绝否则接受执行 if (goal-order 100) { return rclcpp_action::GoalResponse::REJECT; } return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; } // 2. 取消处理回调收到取消请求返回接受/拒绝 rclcpp_action::CancelResponse handle_cancel( const std::shared_ptrGoalHandleFibonacci goal_handle ) { RCLCPP_INFO(this-get_logger(), 【Action回调】收到取消请求); (void)goal_handle; return rclcpp_action::CancelResponse::ACCEPT; } // 3. 目标执行回调目标被接受后启动任务执行 void handle_accepted(const std::shared_ptrGoalHandleFibonacci goal_handle) { // 单独开线程执行任务避免阻塞执行器的其他回调 std::thread{std::bind(ActionServerNode::execute, this, _1), goal_handle}.detach(); } // 任务执行主逻辑生成斐波那契数列周期反馈进度 void execute(const std::shared_ptrGoalHandleFibonacci goal_handle) { RCLCPP_INFO(this-get_logger(), 【Action执行】开始执行任务); rclcpp::Rate loop_rate(1); // 1秒反馈一次进度 const auto goal goal_handle-get_goal(); auto feedback std::make_sharedFibonacci::Feedback(); auto result std::make_sharedFibonacci::Result(); int a 0, b 1; feedback-sequence.push_back(a); feedback-sequence.push_back(b); // 循环生成数列直到达到目标阶数 for (int i 2; i goal-order; i) { // 检查任务是否被取消 if (goal_handle-is_canceling()) { result-sequence feedback-sequence; goal_handle-canceled(result); RCLCPP_INFO(this-get_logger(), 【Action执行】任务被取消); return; } // 计算下一个数更新反馈 int next a b; a b; b next; feedback-sequence.push_back(next); goal_handle-publish_feedback(feedback); RCLCPP_INFO(this-get_logger(), 【Action反馈】当前进度%d/%d, i1, goal-order); loop_rate.sleep(); } // 任务正常完成返回最终结果 if (rclcpp::ok()) { result-sequence feedback-sequence; goal_handle-succeed(result); RCLCPP_INFO(this-get_logger(), 【Action执行】任务完成); } } // Action服务端成员 rclcpp_action::ServerFibonacci::SharedPtr action_server_; }; // ############################## 模块3生命周期节点 Lifecycle Node ############################## // 【面试必考生命周期节点和普通节点的区别状态机原理】 // 1. 本质带状态管理的节点继承自rclcpp_lifecycle::LifecycleNode // 2. 核心有限状态机节点的启动、配置、激活、停用、销毁都有明确的状态和对应回调 // 3. 四大核心状态 // - Unconfigured未配置节点刚创建的初始状态仅完成基础构造 // - Inactive未激活配置完成不执行核心业务不发布话题待机状态 // - Active激活正常工作状态所有功能、通信接口正常运行 // - Finalized已销毁节点资源释放生命周期结束 // 4. 状态转换回调on_configure、on_activate、on_deactivate、on_cleanup、on_shutdown // 5. 适用场景机器人系统有序启动先初始化传感器再启动算法最后执行运动避免启动顺序异常 // 故障时安全停用保证系统安全性工业机器人、量产场景常用 class DemoLifecycleNode : public rclcpp_lifecycle::LifecycleNode { public: DemoLifecycleNode(const std::string node_name) : LifecycleNode(node_name) { RCLCPP_INFO(this-get_logger(), 【生命周期节点】创建完成当前状态Unconfigured 未配置); } // 状态转换回调核心 // 1. 配置回调从Unconfigured → Inactive 时触发 // 用来做参数加载、硬件初始化、接口创建等一次性配置工作 rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_configure(const rclcpp_lifecycle::State previous_state) { (void)previous_state; // 创建生命周期发布者只有节点激活时才会真正发布消息停用后自动停止发布 lifecycle_pub_ this-create_publisherstd_msgs::msg::String(lifecycle_topic, 10); timer_ this-create_wall_timer( std::chrono::seconds(1), std::bind(DemoLifecycleNode::timer_publish, this) ); // 定时器默认不启动激活节点时再启动 timer_-cancel(); RCLCPP_INFO(this-get_logger(), 【生命周期回调】on_configure 配置完成进入Inactive未激活状态); return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; } // 2. 激活回调从Inactive → Active 时触发 // 启动业务逻辑、开始发布订阅、进入正式工作状态 rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_activate(const rclcpp_lifecycle::State previous_state) { (void)previous_state; timer_-reset(); // 启动定时器 RCLCPP_INFO(this-get_logger(), 【生命周期回调】on_activate 激活完成进入Active工作状态); return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; } // 3. 停用回调从Active → Inactive 时触发 // 暂停业务逻辑、停止发布回到待机状态 rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State previous_state) { (void)previous_state; timer_-cancel(); // 停止定时器 RCLCPP_INFO(this-get_logger(), 【生命周期回调】on_deactivate 停用完成回到Inactive未激活状态); return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; } // 4. 清理回调从Inactive → Unconfigured 时触发 // 释放配置资源回到初始未配置状态 rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_cleanup(const rclcpp_lifecycle::State previous_state) { (void)previous_state; lifecycle_pub_.reset(); timer_.reset(); RCLCPP_INFO(this-get_logger(), 【生命周期回调】on_cleanup 清理完成回到Unconfigured未配置状态); return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; } // 5. 关闭回调任意状态 → Finalized 时触发 // 节点销毁前的最终资源释放 rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_shutdown(const rclcpp_lifecycle::State previous_state) { (void)previous_state; RCLCPP_INFO(this-get_logger(), 【生命周期回调】on_shutdown 节点关闭进入Finalized销毁状态); return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; } private: void timer_publish() { auto msg std_msgs::msg::String(); msg.data 生命周期节点激活中发布的消息; lifecycle_pub_-publish(msg); } rclcpp_lifecycle::LifecyclePublisherstd_msgs::msg::String::SharedPtr lifecycle_pub_; rclcpp::TimerBase::SharedPtr timer_; }; // ############################## 模块4执行器完整用法 DDS/QoS核心说明 ############################## // 【面试必考DDS是什么ROS2和DDS的关系】 // 1. DDSData Distribution Service数据分发服务ROS2的底层通信中间件 // 2. ROS1 底层是自研的TCPROS/UDPROS依赖中心化的Master节点存在单点故障风险 // 3. ROS2 底层直接采用工业级DDS标准去中心化架构没有Master所有节点对等通信 // 4. DDS核心优势高实时性、高可靠性、原生支持QoS服务质量、符合工业自动化标准、支持大规模分布式系统 // 5. Domain ID 域IDROS2通过域ID隔离通信网络同一个域ID的节点才能互相发现和通信默认值为0 // 不同机器上的ROS2节点只要域ID相同、在同一局域网内就能自动发现并通信 // 【面试必考QoS服务质量】 // QoS是DDS提供的核心能力用来配置通信的可靠性、延迟、持久性等策略 // 常用QoS策略 // 1. Reliability 可靠性 // - BEST_EFFORT 尽力而为发了就不管丢包不重传延迟最低适合高频传感器数据 // - RELIABLE 可靠保证消息一定送达丢包自动重传适合指令、状态等不能丢失的数据 // 2. Durability 持久性 // - VOLATILE 临时只保留当前消息新订阅者收不到历史消息 // - TRANSIENT_LOCAL 瞬态本地发布者保留历史消息新订阅者连上就能收到最新的历史消息适合地图、配置参数 // 3. Depth 队列深度缓存的消息数量队列满了就丢弃最旧的消息 // 4. Deadline 截止时间消息必须在指定时间内送达否则视为失效 // 示例1单线程执行器 // 特点单线程串行执行所有回调所有回调按顺序排队不会并行 // 适用简单功能、怕数据竞争、对时序要求严格的场景稳定性最高 int run_single_thread_executor(int argc, char ** argv) { rclcpp::init(argc, argv); // 创建两个不同功能的节点 auto basic_node std::make_sharedBasicCoreNode(basic_node_1); auto action_node std::make_sharedActionServerNode(action_server_node); // 创建单线程执行器 rclcpp::executors::SingleThreadedExecutor executor; // 挂载多个节点 → 1个执行器可以管理多个节点 executor.add_node(basic_node); executor.add_node(action_node); // 【面试考点spin()的本质】 // spin()是阻塞死循环执行器在循环里不断做3件事 // 1. 遍历所有挂载的节点检查所有回调组里的回调任务 // 2. 判断哪个回调满足触发条件有消息到达、定时器到点、有服务请求 // 3. 按调度规则执行对应回调 // 单线程执行器一次只执行一个回调执行完成才取下一个 RCLCPP_INFO(rclcpp::get_logger(executor), 单线程执行器启动进入spin阻塞循环); executor.spin(); rclcpp::shutdown(); return 0; } // 示例2多线程执行器 // 特点开启多个线程不同回调组的回调可以并行执行 // 注意同一个互斥回调组里的回调即使多线程也只能串行只有不同回调组的回调才能并行 // 适用多任务高并发、多传感器数据处理、避免长回调阻塞其他任务 int run_multi_thread_executor(int argc, char ** argv) { rclcpp::init(argc, argv); auto vision_node std::make_sharedBasicCoreNode(vision_node); auto motion_node std::make_sharedBasicCoreNode(motion_node); // 创建多线程执行器指定4个工作线程 rclcpp::executors::MultiThreadedExecutor executor( rclcpp::ExecutorOptions(), 4 ); executor.add_node(vision_node); executor.add_node(motion_node); // 多线程执行器多个线程同时从就绪队列里取回调执行 // 不同回调组的回调可以同时运行大幅提升并发性能 RCLCPP_INFO(rclcpp::get_logger(executor), 多线程执行器启动4线程并行调度); executor.spin(); rclcpp::shutdown(); return 0; } // 示例3静态单线程执行器 // 特点启动前必须挂载完所有节点运行中不能动态增删节点 // 优势省去动态节点管理的额外开销执行效率最高延迟最低 // 适用架构固定的量产程序、嵌入式资源受限场景 int run_static_single_executor(int argc, char ** argv) { rclcpp::init(argc, argv); auto fixed_node1 std::make_sharedBasicCoreNode(fixed_node_1); auto fixed_lifecycle_node std::make_sharedDemoLifecycleNode(fixed_lifecycle_node); rclcpp::executors::StaticSingleThreadedExecutor executor; // spin启动前必须把所有节点添加完成 executor.add_node(fixed_node1); executor.add_node(fixed_lifecycle_node-get_node_base_interface()); // 生命周期节点特殊挂载方式 RCLCPP_INFO(rclcpp::get_logger(executor), 静态单线程执行器启动运行中不可新增节点); executor.spin(); // spin运行后禁止调用add_node静态执行器不支持运行时动态修改节点 rclcpp::shutdown(); return 0; } // ############################## 主函数 ############################## // 编译运行时选择要执行的示例即可 int main(int argc, char ** argv) { // 默认运行单线程执行器示例可自行切换其他示例 return run_single_thread_executor(argc, argv); } /* ############################## ROS2面试高频考点汇总对应以上代码############################## 1. 基础核心 - 节点功能容器存放回调和通信接口本身不执行代码 - 回调函数事件触发自动执行完全依赖执行器调度 - 执行器回调调度器三种类型单线程/多线程/静态单线程spin为阻塞调度循环 - 回调组调度最小单元互斥组串行、可重入组并行多线程执行器的并行前提是回调分属不同组 2. 三大通信机制对比必问 | 类型 | 通信模式 | 同步/异步 | 数量关系 | 核心特点 | 适用场景 | |--------|----------------|-----------|----------|------------------------|------------------------------| | 话题 | 发布-订阅 | 异步 | 一对多 | 无反馈、低延迟、高频 | 传感器数据、状态发布、指令流 | | 服务 | 请求-响应 | 同步 | 一对一 | 有确定结果、会阻塞 | 短任务、参数设置、状态查询 | | Action | 目标-反馈-结果 | 异步 | 一对一 | 有进度、可取消、长耗时 | 导航、机械臂运动、长时任务 | 3. DDS与QoS - ROS2底层基于DDS去中心化无MasterROS1有中心化Master存在单点故障 - Domain ID隔离网络同域节点自动发现通信 - QoS核心策略可靠性、持久性、队列深度按需适配不同通信场景 4. 生命周期节点 - 带有限状态机的节点四大核心状态未配置/未激活/激活/销毁 - 状态转换有明确回调支持系统有序启动、安全停用 - 适合工业场景、启动顺序要求严格的量产系统 5. 参数机制 - ROS2参数是节点级的去中心化每个节点独立维护 - ROS1是中心化参数服务器存在单点故障风险 */find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(std_msgs REQUIRED) find_package(example_interfaces REQUIRED) find_package(rclcpp_action REQUIRED) find_package(rclcpp_lifecycle REQUIRED) add_executable(ros2_interview_demo src/ros2_interview_demo.cpp) ament_target_dependencies(ros2_interview_demo rclcpp std_msgs example_interfaces rclcpp_action rclcpp_lifecycle )