1、创建文件夹、创建功能包
# 创建文件夹mkdirros2_dir# 进入文件夹, 创建src, 进入srccdros2_dirmkdirsrccdsrc# 创建功能包ros2 pkg create pub_sub_pkg --build-type ament_cmake --dependencies rclcpp std_msgs
2、创建节点:发布主题
- publisher_member_function.cpp
/** * @file publisher_member_function.cpp * @brief ROS 2 发布者示例:周期性向"topic"话题发布字符串消息 * @details 基于rclcpp实现,使用类封装Node,通过定时器周期性触发消息发布 */ // 1. 引入核心依赖头文件 #include <rclcpp/rclcpp.hpp> // ROS 2 C++核心库:提供Node、发布者、定时器、日志等核心功能 #include <std_msgs/msg/string.hpp> // ROS 2标准字符串消息类型:定义话题通信的消息格式 #include <chrono> // C++标准时间库:用于设置定时器周期 #include <string> // C++标准字符串库:用于消息内容拼接 // 引入chrono字面量(如500ms),避免写std::chrono::milliseconds(500),简化代码 using namespace std::chrono_literals; /** * @class MinimalPublisher * @brief 自定义发布者类,继承自rclcpp::Node(ROS 2所有功能的核心载体) * @details 封装发布者对象、定时器对象和消息计数器,通过定时器回调实现周期性发布 */ class MinimalPublisher : public rclcpp::Node { public: /** * @brief 构造函数:初始化节点、创建发布者、创建定时器 * @details * 1. 调用父类Node的构造函数,设置节点名称为"minimal_publisher"(全局唯一) * 2. 初始化消息计数器count_为0 * 3. 创建发布者对象,绑定话题名和队列大小 * 4. 创建定时器对象,绑定回调函数和触发周期 */ MinimalPublisher() : Node("minimal_publisher"), // 初始化父类Node,节点命名为"minimal_publisher" count_(0) // 初始化消息计数器,记录发布的消息数量 { // 创建发布者对象 // 模板参数:发布的消息类型(必须与订阅者一致) // 参数1:话题名称"topic"(订阅者需订阅该话题才能接收消息) // 参数2:队列大小10(消息积压时最多缓存10条,超出则丢弃旧消息,避免内存溢出) publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10); // 创建定时器对象(壁钟定时器,基于系统时间) // 参数1:定时器周期500ms(每500毫秒触发一次回调函数) // 参数2:回调函数绑定(将类的成员函数timer_callback绑定到当前对象) timer_ = this->create_wall_timer( 500ms, std::bind(&MinimalPublisher::timer_callback, this) // bind保证回调函数能访问类的成员变量 ); // 打印初始化日志(可选,用于确认发布者创建成功) RCLCPP_INFO(this->get_logger(), "MinimalPublisher node initialized, start publishing..."); } private: /** * @brief 定时器回调函数:每次定时器触发时执行,负责构建并发布消息 * @details * 1. 创建字符串消息对象 * 2. 拼接消息内容(包含计数器,每次发布后自增) * 3. 打印发布日志(便于调试) * 4. 发布消息到"topic"话题 */ void timer_callback() { // 创建std_msgs::msg::String类型的消息对象(自动生命周期管理) auto message = std_msgs::msg::String(); // 拼接消息内容:计数器count_自增,生成带计数的字符串 message.data = "Hello, ROS 2! Count: " + std::to_string(count_++); // 打印INFO级日志:输出当前发布的消息内容 // this->get_logger()获取节点的日志器,RCLCPP_INFO是ROS 2日志宏(支持DEBUG/WARN/ERROR/FATAL) RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); // 发布消息:将消息发送到"topic"话题,所有订阅该话题的节点都会收到 publisher_->publish(message); } // 类成员变量(私有化,避免外部直接修改) rclcpp::TimerBase::SharedPtr timer_; // 定时器智能指针:ROS 2推荐用智能指针管理资源,自动释放 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; // 发布者智能指针 size_t count_; // 消息计数器:记录已发布的消息数量(size_t是无符号整型,适合计数) }; /** * @brief 主函数:ROS 2程序入口,负责初始化、运行节点、释放资源 * @param argc 命令行参数个数 * @param argv 命令行参数数组 * @return int 程序退出码(0表示正常退出) * @details * 1. 初始化ROS 2上下文(全局资源,必须先调用) * 2. 创建发布者节点的智能指针(ROS 2推荐用智能指针管理节点) * 3. 自旋节点(进入事件循环,处理定时器回调等异步事件) * 4. 关闭ROS 2上下文,释放资源 */ int main(int argc, char * argv[]) { // 初始化ROS 2上下文:解析命令行参数,初始化全局资源(如DDS通信层) rclcpp::init(argc, argv); // 自旋节点:阻塞当前线程,持续运行事件循环 // std::make_shared创建MinimalPublisher对象的智能指针,自动管理内存 // spin会处理所有异步事件(定时器回调、消息发布等),直到节点被关闭(如Ctrl+C) rclcpp::spin(std::make_shared<MinimalPublisher>()); // 关闭ROS 2上下文:释放全局资源,必须与init配对使用 rclcpp::shutdown(); // 程序正常退出 return 0; }
3、创建节点:订阅主题
- subscriber_member_function.cpp
/** * @file subscriber_member_function.cpp * @brief ROS 2 订阅者示例:订阅"topic"话题并接收/打印字符串消息 * @details 基于rclcpp实现,使用类封装Node,通过回调函数处理接收到的话题消息 */ // 1. 引入核心依赖头文件 #include <rclcpp/rclcpp.hpp> // ROS 2 C++核心库:提供Node、订阅者、日志等核心功能 #include <std_msgs/msg/string.hpp> // ROS 2标准字符串消息类型:与发布者保持一致的消息格式 /** * @class MinimalSubscriber * @brief 自定义订阅者类,继承自rclcpp::Node(ROS 2所有功能的核心载体) * @details 封装订阅者对象,通过回调函数响应话题消息,实现异步接收消息 */ class MinimalSubscriber : public rclcpp::Node { public: /** * @brief 构造函数:初始化节点、创建订阅者 * @details * 1. 调用父类Node的构造函数,设置节点名称为"minimal_subscriber"(全局唯一) * 2. 创建订阅者对象,绑定话题名、队列大小和消息回调函数 */ MinimalSubscriber() : Node("minimal_subscriber") // 初始化父类Node,节点命名为"minimal_subscriber" { // 创建订阅者对象 // 模板参数:订阅的消息类型(必须与发布者完全一致,否则无法解析消息) // 参数1:话题名称"topic"(必须与发布者的话题名一致,才能接收对应消息) // 参数2:队列大小10(缓存未处理的消息,超出则丢弃旧消息,避免内存溢出) // 参数3:消息回调函数绑定(收到消息时触发topic_callback,_1为消息参数占位符) subscription_ = this->create_subscription<std_msgs::msg::String>( "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1) ); // 打印初始化日志(可选,用于确认订阅者创建成功) RCLCPP_INFO(this->get_logger(), "MinimalSubscriber node initialized, start listening..."); } private: /** * @brief 话题消息回调函数:收到消息时自动执行,处理接收到的字符串消息 * @param msg 接收到的消息智能指针(ROS 2推荐用SharedPtr,避免拷贝+自动内存管理) * @details 打印接收到的消息内容,支持后续扩展(如消息解析、业务逻辑处理等) */ void topic_callback(const std_msgs::msg::String::SharedPtr msg) { // 打印INFO级日志:输出接收到的消息内容 // this->get_logger()获取节点日志器,msg->data.c_str()将C++ string转为C风格字符串 RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); } // 类成员变量(私有化,避免外部直接修改) rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; // 订阅者智能指针 // ROS 2通过智能指针管理资源,自动释放 }; /** * @brief 主函数:ROS 2程序入口,负责初始化、运行节点、释放资源 * @param argc 命令行参数个数 * @param argv 命令行参数数组 * @return int 程序退出码(0表示正常退出) * @details * 1. 初始化ROS 2上下文(全局资源,必须先调用) * 2. 创建订阅者节点的智能指针(ROS 2推荐用智能指针管理节点生命周期) * 3. 自旋节点(进入事件循环,持续监听话题消息,触发回调函数) * 4. 关闭ROS 2上下文,释放全局资源 */ int main(int argc, char * argv[]) { // 初始化ROS 2上下文:解析命令行参数,初始化DDS通信层、节点管理器等全局资源 rclcpp::init(argc, argv); // 自旋节点:阻塞当前线程,持续运行事件循环 // std::make_shared创建MinimalSubscriber对象的智能指针,自动管理内存 // spin会监听"topic"话题的消息,收到消息时触发topic_callback,直到节点被关闭(如Ctrl+C) rclcpp::spin(std::make_shared<MinimalSubscriber>()); // 关闭ROS 2上下文:释放全局资源,必须与init配对使用 rclcpp::shutdown(); // 程序正常退出 return 0; }
4、CMakeLists.txt 追加内容
# ==============================================================================# 编译可执行文件 & 配置依赖 & 安装# 核心逻辑:编译源码生成可执行文件 → 链接 ROS 2 依赖库 → 安装到标准路径(支持 ros2 run 调用)# ==============================================================================# 1. 编译发布者可执行文件# add_executable:CMake 核心指令,将指定源码编译为可执行文件# 参数1:可执行文件名称(自定义,对应 ros2 run 命令的第二个参数,如 ros2 run 包名 talker)# 参数2:待编译的源码文件路径(src/ 是ROS 2包的标准源码目录)add_executable(talker src/publisher_member_function.cpp)# 2. 为发布者可执行文件绑定ROS 2依赖# ament_target_dependencies:ROS 2封装的CMake指令,替代原生CMake的include_directories+target_link_libraries# 作用:# - 自动查找 rclcpp/std_msgs 包的头文件路径(如 /opt/ros/humble/include)# - 自动链接依赖库(如 librclcpp.so、libstd_msgs.so)# - 自动设置编译选项(如C++标准、ROS 2宏定义)# 参数1:目标可执行文件名称(与add_executable的第一个参数一致)# 参数2+:依赖的ROS 2包名(必须与find_package中声明的包一致)ament_target_dependencies(talker rclcpp std_msgs)# 3. 编译订阅者可执行文件# 逻辑同发布者:将订阅者源码编译为名为 listener 的可执行文件add_executable(listener src/subscriber_member_function.cpp)# 4. 为订阅者可执行文件绑定ROS 2依赖# 依赖包与发布者一致(rclcpp是核心通信库,std_msgs是消息类型库)ament_target_dependencies(listener rclcpp std_msgs)# 5. 安装可执行文件到系统路径# install:CMake核心指令,将编译产物安装到指定目录# TARGETS:指定要安装的目标(可执行文件talker、listener)# DESTINATION:安装路径(ROS 2标准路径:lib/${PROJECT_NAME})# 关键作用:# - 安装后可通过 ros2 run 包名 可执行文件名 调用(ROS 2会在lib/包名目录下查找可执行文件)# - ${PROJECT_NAME}是CMake变量,自动替换为当前包名(避免硬编码,提高可移植性)install(TARGETS talker# 要安装的发布者可执行文件listener# 要安装的订阅者可执行文件DESTINATION lib/${PROJECT_NAME})# ROS 2标准安装路径
5、colcon build 编译
# 回到:工作空间目录【ros2_dir】 下执行colcon build --packages-select pub_sub_pkg
6、运行:发布者、订阅者
# 刷入:临时环境变量, 方便ros2 找到sourceinstall/setup.bash# 执行:发布者ros2 run pub_sub_pkg talker
# 刷入:临时环境变量, 方便ros2 找到sourceinstall/setup.bash# 执行:发布者ros2 run pub_sub_pkg listener