ROS2自学笔记:深入解析通信接口机制与实践

一、ROS2通信接口核心机制概述

ROS2的通信接口设计基于DDS(Data Distribution Service)标准,通过发布-订阅(Pub/Sub)、服务(Service)和动作(Action)三种模式实现节点间解耦通信。相较于ROS1,ROS2的通信架构去除了中心化的Master节点,采用分布式发现机制,显著提升了系统的鲁棒性和扩展性。

1.1 通信模型对比

模式 特点 适用场景
话题(Topic) 异步单向通信,支持多对多 传感器数据流、日志传输
服务(Service) 同步请求-响应,一对一 配置查询、功能调用
动作(Action) 带反馈的异步长时操作 导航、机械臂控制等耗时任务

1.2 DDS中间件的作用

DDS作为底层通信框架,提供以下核心能力:

  • 服务质量(QoS)策略:支持可靠性、持久性、截止时间等配置
  • 发现服务:自动检测网络中的节点和主题
  • 数据序列化:通过CDR(Common Data Representation)实现跨语言传输

二、话题通信(Topic)详解与实践

话题通信是ROS2中最常用的异步通信方式,适用于高频数据传输场景。

2.1 基础实现步骤

  1. 创建发布者节点
    ```cpp

    include “rclcpp/rclcpp.hpp”

    include “std_msgs/msg/string.hpp”

class PublisherNode : public rclcpp::Node {
public:
PublisherNode() : Node(“publishernode”) {
publisher
= this->createpublisher:msg::String>(“topic_name”, 10);
timer
= this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&PublisherNode::publish_message, this));
}

private:
void publishmessage() {
auto msg = std_msgs::msg::String();
msg.data = “Hello ROS2”;
publisher
->publish(msg);
}
rclcpp::Publisher:msg::String>::SharedPtr publisher;
rclcpp::TimerBase::SharedPtr timer
;
};

  1. 2. **创建订阅者节点**
  2. ```cpp
  3. class SubscriberNode : public rclcpp::Node {
  4. public:
  5. SubscriberNode() : Node("subscriber_node") {
  6. subscription_ = this->create_subscription<std_msgs::msg::String>(
  7. "topic_name", 10,
  8. std::bind(&SubscriberNode::topic_callback, this, std::placeholders::_1));
  9. }
  10. private:
  11. void topic_callback(const std_msgs::msg::String::SharedPtr msg) {
  12. RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
  13. }
  14. rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
  15. };

2.2 QoS配置最佳实践

  • 可靠性策略
    • Reliable:确保消息不丢失(适用于关键数据)
    • BestEffort:允许丢包(适用于非关键数据)
  • 历史策略
    • KeepLast:保留最新N条消息
    • KeepAll:保留所有历史消息
  1. rclcpp::QoS qos_profile(10); // 队列深度
  2. qos_profile.reliable(); // 可靠性设置
  3. auto publisher = node->create_publisher<MsgType>("topic", qos_profile);

三、服务通信(Service)实现要点

服务通信适用于同步的请求-响应场景,需注意超时处理和线程安全。

3.1 服务端实现

  1. #include "example_interfaces/srv/add_two_ints.hpp"
  2. class ServiceServer : public rclcpp::Node {
  3. public:
  4. ServiceServer() : Node("service_server") {
  5. service_ = create_service<example_interfaces::srv::AddTwoInts>(
  6. "add_two_ints",
  7. std::bind(&ServiceServer::handle_service, this, std::placeholders::_1, std::placeholders::_2));
  8. }
  9. private:
  10. void handle_service(
  11. const std::shared_ptr<rmw_request_id_t> request_header,
  12. const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
  13. const std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response) {
  14. response->sum = request->a + request->b;
  15. }
  16. rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service_;
  17. };

3.2 客户端调用与超时处理

  1. class ServiceClient : public rclcpp::Node {
  2. public:
  3. ServiceClient() : Node("service_client") {
  4. client_ = create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
  5. // 等待服务可用
  6. while (!client_->wait_for_service(std::chrono::seconds(1))) {
  7. if (!rclcpp::ok()) {
  8. RCLCPP_ERROR(get_logger(), "Interrupted while waiting for service");
  9. return;
  10. }
  11. RCLCPP_INFO(get_logger(), "Service not available, waiting...");
  12. }
  13. }
  14. void send_request() {
  15. auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
  16. request->a = 1;
  17. request->b = 2;
  18. auto future = client_->async_send_request(request);
  19. if (rclcpp::spin_until_future_complete(get_node_base_interface(), future, std::chrono::seconds(1))) {
  20. if (future.valid()) {
  21. auto response = future.get();
  22. RCLCPP_INFO(get_logger(), "Sum: %ld", response->sum);
  23. } else {
  24. RCLCPP_ERROR(get_logger(), "Service call failed");
  25. }
  26. } else {
  27. RCLCPP_ERROR(get_logger(), "Service call timed out");
  28. }
  29. }
  30. rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
  31. };

四、动作通信(Action)高级应用

动作通信结合了话题的异步性和服务的反馈机制,适用于长时间运行的任务。

4.1 动作接口定义

需创建.action文件定义输入、输出和反馈类型:

  1. # add_two_ints.action
  2. int64 a
  3. int64 b
  4. ---
  5. int64 sum
  6. ---
  7. int64 current_sum

4.2 动作服务器实现

  1. #include "action_tutorials/action/fibonacci.hpp"
  2. class FibonacciAction : public rclcpp::Node {
  3. public:
  4. FibonacciAction() : Node("fibonacci_action") {
  5. action_server_ = rclcpp_action::create_server<action_tutorials::action::Fibonacci>(
  6. this,
  7. "fibonacci",
  8. std::bind(&FibonacciAction::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
  9. std::bind(&FibonacciAction::handle_cancel, this, std::placeholders::_1),
  10. std::bind(&FibonacciAction::handle_accepted, this, std::placeholders::_1));
  11. }
  12. private:
  13. // 省略具体实现代码...
  14. rclcpp_action::Server<action_tutorials::action::Fibonacci>::SharedPtr action_server_;
  15. };

4.3 动作客户端使用

  1. class FibonacciClient : public rclcpp::Node {
  2. public:
  3. FibonacciClient() : Node("fibonacci_client") {
  4. client_ptr_ = rclcpp_action::create_client<action_tutorials::action::Fibonacci>(
  5. this,
  6. "fibonacci");
  7. }
  8. auto send_goal() {
  9. using namespace std::chrono_literals;
  10. auto goal_msg = action_tutorials::action::Fibonacci::Goal();
  11. goal_msg.order = 10;
  12. auto send_goal_options = rclcpp_action::Client<action_tutorials::action::Fibonacci>::SendGoalOptions();
  13. send_goal_options.goal_response_callback =
  14. [this](const rclcpp_action::GoalResponse & response) {
  15. // 处理目标响应
  16. };
  17. send_goal_options.feedback_callback =
  18. [this](rclcpp_action::ClientGoalHandle<>::SharedPtr,
  19. const std::shared_ptr<const action_tutorials::action::Fibonacci::Feedback> feedback) {
  20. // 处理反馈
  21. };
  22. send_goal_options.result_callback =
  23. [this](const rclcpp_action::ClientGoalHandle<>::WrappedResult & result) {
  24. // 处理结果
  25. };
  26. auto goal_handle_future = client_ptr_->async_send_goal(goal_msg, send_goal_options);
  27. }
  28. rclcpp_action::Client<action_tutorials::action::Fibonacci>::SharedPtr client_ptr_;
  29. };

五、通信接口性能优化策略

  1. QoS策略调优

    • 降低可靠性要求以减少延迟
    • 调整历史队列深度防止内存溢出
  2. 多线程处理

    1. rclcpp::executors::MultiThreadedExecutor executor;
    2. executor.add_node(publisher_node);
    3. executor.add_node(subscriber_node);
    4. executor.spin();
  3. 跨语言通信

    • 使用rosidl生成不同语言的接口定义
    • 确保消息类型的CDR序列化兼容性

六、常见问题与解决方案

  1. 消息丢失问题

    • 检查QoS配置是否匹配
    • 增加deadlineliveliness策略
  2. 服务调用超时

    • 合理设置wait_for_service超时时间
    • 实现重试机制
  3. 动作执行中断

    • 实现handle_cancel回调
    • 保存执行状态以便恢复

通过系统学习ROS2的通信接口机制,开发者能够构建出高效、可靠的机器人系统。建议从话题通信入手,逐步掌握服务、动作等高级特性,并结合实际场景进行QoS参数调优。对于复杂系统,可参考行业常见技术方案中的多节点通信架构设计,提升系统整体性能。