手把手做ROS2机器人系统开发(四):C++实现发布者与订阅者
发布者(Publisher)与订阅者(Subscriber)是 ROS2 话题通信的核心模型,也是所有机器人应用(传感器数据分发、状态上报、控制指令下发等)的基础。本章节使用 C++ 编写一个最小可运行的发布者/订阅者示例,作为后续开发的标准模板,帮助大家清晰掌握节点程序的完整编写流程。
手把手做ROS2机器人系统开发(四):C++实现发布者与订阅者
教程背景
发布者(Publisher)与订阅者(Subscriber)是 ROS2 话题通信的核心模型,也是所有机器人应用(传感器数据分发、状态上报、控制指令下发等)的基础。本章节使用 C++ 编写一个最小可运行的发布者/订阅者示例,作为后续开发的标准模板,帮助大家清晰掌握节点程序的完整编写流程。
一、程序编写
1. 创建代码目录
mkdir src
cd src/
2. 创建软件包
ros2 pkg create --build-type ament_cmake cpp_pubsub
3. 测试编译
colcon build
注意:编译操作需在包含
src目录的上级工作空间根目录执行。
4. 打印与还原包环境目录
当需要剔除已删除软件包的引用时,可先查看再重置环境变量:
printenv AMENT_PREFIX_PATH CMAKE_PREFIX_PATH
export AMENT_PREFIX_PATH=/opt/ros/humble
export CMAKE_PREFIX_PATH=/opt/ros/humble
5. 设置系统全局头文件目录
为了让 VS Code 正确识别 ROS2 头文件,将全局头文件目录加入配置:
/opt/ros/humble/include/
在 .vscode/c_cpp_properties.json 中配置:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/opt/ros/humble/include/**",
"/opt/ros/humble/include/std_msgs"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
6. 发布者程序编写
创建文件 src/cpp_pubsub/src/publisher_member_function.cpp:
/**
* @file publisher_member_function.cpp
* @author gmotion (motion_gui@126.com)
* @brief 发布者实例程序
* @version 0.1
* @date 2022-06-09
* @copyright gmotion Copyright (c) 2022
*/
#include <chrono> //处理时间类
#include <functional> //函数对象
#include <memory> //内存管理
#include <string> //字符串
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
using namespace std::chrono_literals; //处理时间类
//ROS2 发布节点信息
class MinimalPublisher : public rclcpp::Node
{
public:
//默认构造函数和析构函数
MinimalPublisher();
~MinimalPublisher();
private:
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
void timer_callback();
};
//默认构造函数
MinimalPublisher::MinimalPublisher()
: Node("minimal_publisher"),count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic",10);
timer_ = this->create_wall_timer(500ms,std::bind(&MinimalPublisher::timer_callback,this));
}
//默认析构函数
MinimalPublisher::~MinimalPublisher()
{
}
//默认回调函数
void MinimalPublisher::timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello world,gmotion! =>" + std::to_string(count_ ++ );
RCLCPP_INFO(this->get_logger(),"Publisher: '%s' ",message.data.c_str());
publisher_->publish(message);
}
//主运行程序
int main(int argc,char * argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
7. 订阅者程序编写
创建文件 src/cpp_pubsub/src/subscriber_member_function.cpp:
/**
* @file subscriber_member_function.cpp
* @author gmotion (motion_gui@126.com)
* @brief 订阅者实例程序
* @version 0.1
* @date 2022-06-10
* @copyright Copyright (c) 2022
*/
#include <functional> //函数对象
#include <memory> //内存管理
#include <string> //字符串
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
using std::placeholders::_1; //C++占位符
//ROS2 订阅者节点信息
class MinimalSubscriber : public rclcpp::Node
{
public:
//默认构造函数和析构函数
MinimalSubscriber();
~MinimalSubscriber();
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr Subscriber_;
void topic_callback(std_msgs::msg::String::SharedPtr msg);
};
//默认构造函数
MinimalSubscriber::MinimalSubscriber()
: Node("minimal_Subscriber")
{
Subscriber_ = this->create_subscription<std_msgs::msg::String>(
"topic",10,std::bind(&MinimalSubscriber::topic_callback,this,_1));
}
//默认析构函数
MinimalSubscriber::~MinimalSubscriber()
{
}
//默认回调函数
void MinimalSubscriber::topic_callback(std_msgs::msg::String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(),"Subscriber Read: '%s' ",msg->data.c_str());
}
//主运行程序
int main(int argc,char * argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
8. 软件包依赖设置
编辑 src/cpp_pubsub/package.xml,添加运行依赖:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp_pubsub</name>
<version>0.0.0</version>
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="motion_gui@126.com">Gmotion</maintainer>
<license>Apache License 2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
9. 设置编译选项
编辑 src/cpp_pubsub/CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
set(ament_cmake_copyright_FOUND TRUE)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
二、程序测试
1. 编译程序
在工作空间根目录执行:
colcon build
2. 引用软件包
. install/setup.bash
3. 开启两个节点测试
终端 1:运行发布者(talker)
. install/setup.bash
ros2 run cpp_pubsub talker
终端 2:运行订阅者(listener)
. install/setup.bash
ros2 run cpp_pubsub listener
程序准确执行,发布者持续发布消息、订阅者持续接收并打印消息,达到预期结果。
小结
本章节实现了 ROS2 中最基础的发布者/订阅者节点,完整覆盖了"创建包 → 编写代码 → 配置依赖 → 编译 → 运行测试"的标准化流程。该模板可直接复用于传感器数据采集、状态发布等场景。下一章节将讲解服务器与客户端的实现。
SEO关键词: ROS2 C++发布者订阅者, ROS2话题通信教程, rclcpp节点编程, ROS2 colcon编译, ROS2功能包创建
推荐阅读
更多工业 AI、智能制造与技术实践文章
TRIO-Basic 从入门到精通(十):UDP通讯实现网络串口化测试与应用
本教程讲解TRIO控制器通过UDP通讯实现网络串口化的完整方案:使用OPEN#20 AS "dgram:IP(端口)"指令发送UDP数据包,WAIT UNTIL实现20ms定时播报,再用Wireshark与WinPcap上位机程序抓包验证。实测数据包准时稳定、无丢包,适用于控制器与第三方设备实时数据交互。
C#工业视觉平台结合YOLOv26与OpenVINO
从数据标注、自动训练到Intel NPU边缘推理的完整工业AI方案:C#工业软件平台 + YOLOv26训练框架 + OpenVINO模型转换 + Intel NPU边缘推理,实现图像采集、自动标注、模型训练到现场推理的完整AI视觉闭环。
RIP软件底层逻辑全解析:从CMYK墨量控制到PRN灰度网点数据
本文深入数码印刷RIP软件的底层逻辑:CMYK曲线与单通道/总墨量限制如何控制物理边界,打印机线性化与ICC特性文件如何实现精准色彩复制,灰度PRN数据与位深度(1/2/3位)如何驱动可变网点喷头,以及半色调方法、PPI/DPI匹配和常见印刷故障的排查指南。
C#调用Intel NPU实现YOLOv26极速推理:OpenVINO工业视觉加速实践
通过C# + OpenVINO Runtime调用Intel Core Ultra NPU运行YOLOv26模型,实现低功耗、毫秒级的工业边缘AI视觉推理,涵盖模型导出、环境配置、推理代码与FP16/INT8/异步推理等性能优化实践。
TRIO-Basic 从入门到精通(三):轴参数的含义
本文系统讲解 TRIO 运动控制器伺服轴参数表中各参数的含义与用途,涵盖单位当量 UNITS/AXIS_UNITS、增益参数、速度加减速、软件/硬件限位、原点信号、位置偏差报警、点动与色标捕捉等,并说明 UNITS 在 FRAME 坐标系开启前后的区别,是配置伺服轴与调试运动表现的必备基础。
LinuxCNC 开发系列教程(一):Ubuntu 环境下安装 EtherCAT 主站
本文详细记录在 Ubuntu + RTAI 实时内核环境下,从源码编译安装 IGH EtherLab 开源 EtherCAT 主站的完整流程,涵盖依赖安装、源码获取、编译配置、内核模块安装与网卡配置,帮助工业自动化开发者快速搭建实时以太网运动控制平台。
