演示一下如何编写 Publisher (发布者) 和 Subscriber (订阅者) 的代码吗?-02

这是一个非常经典的 ROS 入门场景。我们将创建一个“说话者”(Publisher)和一个“倾听者”(Subscriber)。

为了让你看得清晰,我将沿用上一轮假设的包名 my_robot_control

核心概念图解

在写代码之前,先理解数据流向:

  1. Publisher (Talker): 告诉 ROS Master 我要往某个话题(Topic)发数据。
  2. Topic: 就像一个广播频道(例如名为 /chatter)。
  3. Subscriber (Listener): 告诉 ROS Master 我要监听这个频道,一旦有数据,就调用我的回调函数处理。

第一步:编写 Publisher 代码 (talker.cpp)

~/catkin_ws/src/my_robot_control/src/ 目录下新建 talker.cpp

这个节点会每秒发送一条 "Hello ROS" 消息。

/**
 * 文件名: talker.cpp
 * 功能: 持续发布 std_msgs/String 类型的消息到 "chatter" 话题
 */

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char **argv)
{
  // 1. 初始化节点,命名为 "talker"
  // 注意:节点名称必须在 ROS 网络中唯一
  ros::init(argc, argv, "talker");

  // 2. 创建节点句柄,它是管理节点资源的入口
  ros::NodeHandle nh;

  // 3. 创建 Publisher
  // 语法: nh.advertise<消息类型>("话题名称", 队列长度)
  // 队列长度 1000 意味着:如果网络堵塞,保留最近 1000 条消息,旧的丢弃
  ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 1000);

  // 4. 设置循环频率 (10Hz,即每秒 10 次)
  ros::Rate loop_rate(10);

  int count = 0;
  while (ros::ok()) // 当 ROS 正常运行时(比如没有按 Ctrl+C)
  {
    // 5. 封装消息数据
    std_msgs::String msg;
    std::stringstream ss;
    ss << "Hello ROS " << count;
    msg.data = ss.str();

    // 打印到终端 (类似 printf)
    ROS_INFO("%s", msg.data.c_str());

    // 6. 正式发布消息
    chatter_pub.publish(msg);

    // 7. 处理回调 (虽然这里没有订阅,但保留是个好习惯)
    ros::spinOnce();

    // 8. 休眠直到达到 10Hz 的时间间隔
    loop_rate.sleep();
    
    ++count;
  }

  return 0;
}


第二步:编写 Subscriber 代码 (listener.cpp)

在同一目录下新建 listener.cpp

这个节点会一直等待,一旦 /chatter 话题有消息,就触发回调函数打印出来。

/**
 * 文件名: listener.cpp
 * 功能: 订阅 "chatter" 话题并打印收到的内容
 */

#include "ros/ros.h"
#include "std_msgs/String.h"

// 1. 回调函数 (Callback)
// 当有新消息到达时,ROS 会自动调用这个函数
// msg 是消息的常量指针,效率高
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
  // 初始化节点,命名为 "listener"
  ros::init(argc, argv, "listener");

  ros::NodeHandle nh;

  // 2. 创建 Subscriber
  // 语法: nh.subscribe("话题名称", 队列长度, 回调函数)
  ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);

  // 3. 进入自旋 (Blocking)
  // ros::spin() 会阻塞在这里,不断检查是否有消息到达。
  // 一旦有消息,它就去执行 chatterCallback,执行完回来继续等。
  ros::spin();

  return 0;
}


第三步:修改 CMakeLists.txt (最重要!)

代码写好了,必须告诉编译器如何编译这两个文件。打开 ~/catkin_ws/src/my_robot_control/CMakeLists.txt

你需要确保 find_package 中包含了 std_msgs(因为我们用了 String 消息)。

然后在 Build 区域添加以下内容:

## ... 前面的 find_package ...

## 1. 编译 talker
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
# add_dependencies(talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## 2. 编译 listener
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
# add_dependencies(listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})


第四步:编译与运行

  1. 编译
cd ~/catkin_ws
catkin_make

(确保没有报错,如果有 "undefined reference",检查 CMakeLists.txt 是否保存)
2. 运行 (需要打开 3 个终端窗口)

  • 终端 1 (Master):
roscore

  • 终端 2 (Listener/Subscriber):
source ~/catkin_ws/devel/setup.bash
rosrun my_robot_control listener

(此时它会卡住等待,这是正常的)

  • 终端 3 (Talker/Publisher):
source ~/catkin_ws/devel/setup.bash
rosrun my_robot_control talker

结果
你会看到终端 3 不断打印 "Hello ROS 0", "Hello ROS 1"...
同时,终端 2 会同步打印 "I heard: [Hello ROS 0]"...


关键点总结

概念 Publisher (Talker) Subscriber (Listener)
主动性 主动发送数据 被动等待数据
核心对象 ros::Publisher ros::Subscriber
循环方式 while(ros::ok()) + loop_rate.sleep() ros::spin() (死循环等待)
关键函数 .publish(msg) callback(msg)

接下来,你想了解如何定义自己的“自定义消息”(比如发送一个包含 姓名(string) 和 年龄(int) 的消息),还是想了解如何使用 roslaunch 一次性启动这两个节点?

posted @ 2025-12-20 11:33  jack-chen666  阅读(1)  评论(0)    收藏  举报