2.1 使用C++实现话题通信
2.1.1 话题发布方实现
cpp
// 1、包含头文件(补充字符串消息头文件)
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
// 3、定义节点类
class Talker : public rclcpp::Node
{
public:
// 修正:派生类构造函数正确写法
Talker() : Node("talker_node_cpp")
{
// 3-1 创建发布方(发布std_msgs/msg/String类型,话题名"chatter",队列大小10)
pub_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
// 3-2 创建定时器(固定频率1Hz,绑定回调函数)
/*
参数:1、时间间隔
2、回调函数
返回一个指针对象
*/
timer_ = this->create_wall_timer(
std::chrono::seconds(1),
std::bind(&Talker::timer_callback, this)
);
// 日志打印,确认节点启动
RCLCPP_INFO(this->get_logger(), "发布节点已启动,开始以1Hz发送hello world!");
}
private:
// 3-3 组织消息发布的回调函数
void timer_callback()
{
// 初始化消息对象
auto msg = std_msgs::msg::String();
msg.data = "hello world"+std::to_string(count++); // 设置要发送的内容
// 发布消息
pub_->publish(msg);
// 日志打印(可选,验证发送)
RCLCPP_INFO(this->get_logger(), "发送消息:%s", msg.data.c_str());
}
// 成员变量:发布者、定时器(类内私有)
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
size_t count=0;
rclcpp::TimerBase::SharedPtr timer_;
}; // 修正:类定义末尾添加分号
// 主函数(实现ROS2客户端初始化、spin、资源释放)
int main(int argc, char *argv[])
{
// 2、初始化ROS2客户端
rclcpp::init(argc, argv);
// 4、调用spin函数,传入节点对象指针
rclcpp::spin(std::make_shared<Talker>());
// 5、释放资源
rclcpp::shutdown();
return 0;
}
2.1.2 话题订阅方实现
cpp
/*
需求:订阅发布方发布的消息
1、包含头文件
2、初始化Ros2客户端
3、自定义节点类
3-1 创建订阅方
3-2 解析并输出数据
4、调用spin函数,并传入节点对象指针
5、资源释放
*/
// 1、包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
// 3、自定义节点类
class Listener: public rclcpp::Node{
public:
// 修正:构造函数末尾去掉多余的分号
Listener():Node("listener_node_cpp"){
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
// 3-1 创建订阅方
// 修正:占位符添加下划线 _1
subscription_= this->create_subscription<std_msgs::msg::String>(
"chatter",
10,
std::bind(&Listener::do_cb,this,std::placeholders::_1)
);
}
private:
// 3-2 解析数据并输出数据
// 修正:RCLCPP::INFO → RCLCPP_INFO
void do_cb(const std_msgs::msg::String &msg){
RCLCPP_INFO(this->get_logger(),"订阅到的消息:%s",msg.data.c_str());
}
// 修正:Sharedptr → SharedPtr(P大写)
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
// 修正:main函数参数 char const *argv[] → char *argv[]
int main(int argc, char *argv[])
{
// 2、初始化Ros2客户端
rclcpp::init(argc,argv);
// 4、调用spin函数,并传入节点对象指针
rclcpp::spin(std::make_shared<Listener>());
// 5、资源释放
rclcpp::shutdown();
return 0;
}
运行结果:


2.2 使用python实现话题通信
2.2.1 话题发布
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2 Python 发布方实现:以1Hz频率发布带计数的hello world字符串
对应C++功能:
1. 初始化ROS2节点
2. 创建发布者
3. 定时器1秒触发一次
4. 发布带计数的hello world消息
"""
# 1、导入ROS2核心模块和消息类型
import rclpy
from rclpy.node import Node
# 字符串消息类型(和C++的std_msgs/msg/String对应)
from std_msgs.msg import String
# 用于定时器(可选,也可以直接用rclpy的定时器)
import time
# 3、定义发布方节点类(继承ROS2的Node类)
class TalkerNode(Node):
def __init__(self):
# 初始化父类Node,节点名和C++保持一致:talker_node_cpp
super().__init__("talker_node_cpp")
self.count = 0 # 计数变量(对应C++的size_t count=0)
# 3-1 创建发布者(参数:消息类型、话题名、队列大小)
self.pub_ = self.create_publisher(
msg_type=String, # 消息类型:std_msgs/String
topic="chatter", # 话题名:和C++一致
qos_profile=10 # 队列大小:和C++一致
)
# 3-2 创建定时器(参数:定时周期(秒)、回调函数)
# timer_period=1.0 对应C++的std::chrono::seconds(1)
self.timer_ = self.create_timer(
1.0,
callback=self.timer_callback # 绑定回调函数
)
# 日志打印(对应C++的RCLCPP_INFO)
self.get_logger().info("发布节点已启动,开始以1Hz发送hello world!")
# 3-3 定时器回调函数(组织消息并发布)
def timer_callback(self):
# 初始化消息对象(对应C++的auto msg = std_msgs::msg::String())
msg = String()
# 拼接计数(对应C++的"hello world"+std::to_string(count++))
msg.data = f"hello world{self.count}"
self.count += 1 # 计数自增
# 发布消息(对应C++的pub_->publish(msg))
self.pub_.publish(msg)
# 日志打印发送的消息(可选)
self.get_logger().info(f"发送消息:{msg.data}")
# 主函数(程序入口)
def main(args=None):
# 2、初始化ROS2客户端(对应C++的rclcpp::init(argc, argv))
rclpy.init(args=args)
# 创建节点对象(对应C++的std::make_shared<Talker>())
talker_node = TalkerNode()
# 4、调用spin函数,让节点持续运行(对应C++的rclcpp::spin)
rclpy.spin(talker_node)
# 5、释放资源(对应C++的rclcpp::shutdown())
talker_node.destroy_node() # 销毁节点(Python特有,更安全)
rclpy.shutdown() # 关闭ROS2客户端
# 程序入口判断(Python规范)
if __name__ == "__main__":
main()
2.2.2 话题订阅
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2 Python 订阅方实现:订阅chatter话题的hello world计数消息
对应发布方功能:
1. 初始化ROS2节点
2. 创建订阅者,监听chatter话题
3. 收到消息后解析并打印
"""
# 1、导入ROS2核心模块和消息类型
import rclpy
from rclpy.node import Node
# 和发布方一致的字符串消息类型
from std_msgs.msg import String
# 3、定义订阅方节点类(继承ROS2的Node类)
class ListenerNode(Node):
def __init__(self):
# 初始化父类Node,节点名:listener_node_cpp(和C++订阅方命名风格一致)
super().__init__("listener_node_cpp")
# 3-1 创建订阅者(参数:消息类型、话题名、回调函数、队列大小)
self.sub_ = self.create_subscription(
msg_type=String, # 消息类型:和发布方完全一致
topic="chatter", # 订阅的话题名:必须和发布方一致
callback=self.msg_callback, # 收到消息后执行的回调函数
qos_profile=10 # 队列大小:和发布方一致
)
# 日志打印:确认订阅方启动
self.get_logger().info("订阅节点已启动,等待接收chatter话题消息...")
# 3-2 消息回调函数(解析并打印收到的消息)
def msg_callback(self, msg):
# msg是收到的消息对象,msg.data对应发布方的消息内容
self.get_logger().info(f"订阅到消息:{msg.data}")
# 主函数(程序入口)
def main(args=None):
# 2、初始化ROS2客户端
rclpy.init(args=args)
# 创建订阅方节点对象
listener_node = ListenerNode()
# 4、调用spin函数,让节点持续运行(等待接收消息)
rclpy.spin(listener_node)
# 5、释放资源
listener_node.destroy_node() # 销毁节点
rclpy.shutdown() # 关闭ROS2客户端
# 程序入口判断(Python规范)
if __name__ == "__main__":
main()


2.3 话题通信之自定义消息接口
1、创建.msg文件
在功能包下创建msg文件夹,msg文件夹下创建Student.msg文件,文件内容如下:
string name
int32 age
float64 heightS
2、编辑配置文件
(1)、package.xml文件配置
新增以下依赖:
<!-- 3. 消息编译/运行依赖 -->
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 4. 声明为消息接口包 -->
<member_of_group>rosidl_interface_packages</member_of_group>
自定义.msg文件是纯文本格式 (比如int32 age; string name),ROS2 无法直接识别和使用,必须通过工具把它转换成C++/Python 可调用的代码 (比如 C++ 的Student.hpp、Python 的Student.py)。
而你添加的这些依赖 / 声明,就是为了让 ROS2 完成 "文本→代码" 的转换,以及后续能正常使用生成的代码。
1. <build_depend>rosidl_default_generators</build_depend>
- 中文含义 :"编译阶段依赖" →
rosidl_default_generators(ROS2 默认消息生成器) - 核心作用 :提供编译
.msg文件的工具(比如rosidl_generate_interfaces),负责把你的Student.msg转换成 C++/Python 代码。 - 没有它会怎样 :
CMakeLists.txt里的rosidl_generate_interfaces会报错 "找不到该工具",.msg文件无法编译,生成不了对应的代码。
2. <exec_depend>rosidl_default_runtime</exec_depend>
- 中文含义 :"运行阶段依赖" →
rosidl_default_runtime(ROS2 消息运行时库) - 核心作用 :生成的消息代码(比如 C++ 的
Student类)需要依赖这个库才能运行(比如消息的序列化 / 反序列化、网络传输)。 - 没有它会怎样 :编译消息包可能成功,但其他节点调用
Student.msg时会报 "找不到消息运行时库",无法发布 / 订阅自定义消息。
3. <member_of_group>rosidl_interface_packages</member_of_group>
- 中文含义:"声明该包属于'消息接口包组'"
- 核心作用:告诉 ROS2:"这个包是用来定义消息 / 服务接口的,需要按接口包的规则编译和导出"。ROS2 有专门的逻辑处理接口包(比如把生成的消息代码安装到系统路径、让其他包能找到)。
- 没有它会怎样 :编译可能成功,但其他功能包无法通过
find_package(base_interfaces_demo)找到你的自定义消息,调用时会报 "找不到消息类型"。

完整的packages.xml:
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">
<!-- 1. 基础信息 -->
<name>base_interfaces_demo</name>
<version>0.0.0</version>
<description>自定义消息接口包(Student.msg)</description>
<maintainer email="moweiduo@todo.todo">moweiduo</maintainer>
<license>Apache-2.0</license>
<!-- 2. 构建工具依赖 -->
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- 3. 消息编译/运行依赖 -->
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 4. 声明为消息接口包 -->
<member_of_group>rosidl_interface_packages</member_of_group>
<!-- 5. 测试依赖(修正尖括号) -->
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<!-- 6. 导出配置 -->
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
(2)、CMakeLists.txt文件
find_package(rosidl_default_generators REQUIRED)
为接口文件生成源代码
rosidl_generate_interfaces(
${PROJECT_NAME}
"msg/Student.msg"
)

类比 :就像你要做蛋糕,先找 "烤箱 + 模具"(rosidl_default_generators),find_package就是 "确认厨房有这些工具",REQUIRED是 "没工具就不做了,直接告诉用户"。
3、编译
colcon build --packages-select base_interfaces_demo
4、测试
ros2 interface show base_interfaces_demo/msg/Student

同时,在install的include下会生成对应的包(Student.h/Student.hpp)


2.3.1 话题通信之自定义消息(C++)
1、发布方
cpp
/*
需求:以某个固定频率发送文本学生信息,包含学生的姓名、年龄、身高等数据。
*/
// 1. 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
// 命名空间简化
using namespace std::chrono_literals;
using base_interfaces_demo::msg::Student;
// 3. 定义节点类
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("student_publisher"), count_(0)
{
// 3-1. 创建发布方
publisher_ = this->create_publisher<Student>("topic_stu", 10);
// 3-2. 创建定时器(500ms触发一次)
timer_ = this->create_wall_timer(
500ms,
std::bind(&MinimalPublisher::timer_callback, this)
);
}
private:
// 3-3. 组织消息并发布
void timer_callback()
{
auto stu = Student();
stu.name = "张三";
stu.age = count_++;
stu.height = 1.65;
// 打印日志
RCLCPP_INFO(
this->get_logger(),
"学生信息: name=%s, age=%d, height=%.2f",
stu.name.c_str(), stu.age, stu.height
);
// 发布消息
publisher_->publish(stu);
}
// 成员变量
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<Student>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
// 2. 初始化ROS2客户端
rclcpp::init(argc, argv);
// 4. 调用spin函数,传入节点对象指针
rclcpp::spin(std::make_shared<MinimalPublisher>());
// 5. 释放资源
rclcpp::shutdown();
return 0;
}
2、接收方
cpp
/*
需求:订阅发布方发布的学生消息,并输出到终端。
*/
// 1. 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
// 命名空间简化
using std::placeholders::_1;
using base_interfaces_demo::msg::Student;
// 3. 定义节点类
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("student_subscriber")
{
// 3-1. 创建订阅方
subscription_ = this->create_subscription<Student>(
"topic_stu",
10,
std::bind(&MinimalSubscriber::topic_callback, this, _1)
);
}
private:
// 3-2. 处理订阅到的消息
void topic_callback(const Student & msg) const
{
RCLCPP_INFO(
this->get_logger(),
"订阅的学生消息: name=%s, age=%d, height=%.2f",
msg.name.c_str(), msg.age, msg.height
);
}
// 成员变量:订阅方对象
rclcpp::Subscription<Student>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
// 2. 初始化ROS2客户端
rclcpp::init(argc, argv);
// 4. 调用spin函数,传入节点对象指针
rclcpp::spin(std::make_shared<MinimalSubscriber>());
// 5. 释放资源
rclcpp::shutdown();
return 0;
}
