节点之间的消息通信分为几种形式:
- 话题(topic):单向消息发送/接收方式
- 服务(service):双向消息请求/响应方式
- 动作(action):双向消息目标(goal)/结果(result)/反馈(feedback)方式
- 参数服务器(参数共享模式)
种类 区别 话题 异步 单向 连续单向的发送/接收数据的情况 服务 同步 双向 ` 需要对请求给出即时相应的情况 动作 异步 双向 请求与响应之间需要太长的时间,所以难以使用服务的情况,
或需要中途返回值的情况
节点通信的过程
一、运行主节点
节点之间的消息通信当中,管理连接信息的主节点是使用 ROS 必须首先运行的必需元
素。ROS 主节点使用 roscore 命令来运行,并使用 XMLRPC 运行服务器。主节点为了节点与节点的连接,会注册节点的名称、话题、消息类型、 URI 地址和端口,
并在有请求时将此信息通知给其他节点。二、运行发布者节点
发布者节点使用 rosrun 或 roslaunch 命令来运行。发布者节点向主节点注册发布者节
点名称、话题名称、消息类型、URI 地址和端口。主节点和节点使用 XMLRPC 进行通
信。三、运行订阅者节点
订阅者节点使用 rosrun 或 roslaunch 命令来运行。订阅者节点在运行时向主节点注册其订阅者节点名称、话题名称、消息类型、 URI 地址和端口。主节点和节点使用 XMLRPC
进行通信。四、通知发布者信息
主节点向订阅者节点发送此订阅者希望访问的发布者的名称、话题名称、消息类型、
URI 地址和端口等信息。主节点和节点使用 XMLRPC 进行通信。五、订阅者节点的连接请求
订阅者节点根据从主节点接收的发布者信息,向发布者节点请求直接连接。在这种情况下,要发送的信息包括订阅者节点名称、话题名称和消息类型。发布者节点和订阅
者节点使用 XMLRPC 进行通信。六、发布者节点的连接响应
发布者节点将 TCP 服务器的 URI 地址和端口作为连接响应发送给订阅者节点。发布者
节点和订阅者节点使用 XMLRPC 进行通信。七、TCPROS 连接
订阅者节点使用 TCPROS 创建一个与发布者节点对应的客户端,并直接与发布者节点
连接。节点间通信使用一种称为 TCPROS 的 TCP/IP 方式。八、发送消息
发布者节点向订阅者节点发送消息。节点间通信使用一种称为 TCPROS 的 TCP/IP
方式。
下面是实现者(Publisher)发布话题和接收者(Subcriber)接收话题的例子
一、发布者
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char *argv[])
{
// 1.设置编码
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
// 参数 1 和参数 2 后期为节点传值会使用
// 参数 3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
ros::init(argc, argv, "publisher");
//3.实例化 ROS 句柄
ros::NodeHandle nh; //该类封装了 ROS 中的一些常用功能
//4.实例化 发布者 对象
//泛型: 发布的消息类型
//参数 1: 要发布到的话题
//参数 2: 队列中最大保存的消息数,超出此阈值时,先进的先销毁(时间早的先销毁)
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
//5.组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
std::string msg_front = "Hello,你好啊!"; // 消息前缀
int count = 0; //消息计数器
//逻辑(一秒 10 次)
ros::Rate r(1);
//节点不死;
while(ros::ok())
{
//使用 stringstream 拼接字符串与编号
std::stringstream ss;
ss<<msg_front<<count;
msg.data = ss.str();
//发布消息
pub.publish(msg);
//加入调试,打印发送的消息
ROS_INFO("发送的消息:%s",msg.data.c_str());
//根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
r.sleep();
count++;//循环结束前,让 count 自增
ros::spinOnce();
}
return 0;
}
二、接收者
// 1.包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
void doMsg(const std_msgs::String::ConstPtr& msg_p){
ROS_INFO("我订阅:%s",msg_p->data.c_str());
// ROS_INFO("我听见:%s",(*msg_p).data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
ros::init(argc,argv,"subscriber");
//3.实例化 ROS 句柄
ros::NodeHandle nh;
//4.实例化 订阅者 对象
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
//5.处理订阅的消息(回调函数)
//6.设置循环调用回调函数
ros::spin(); //循环读取接收的数据,并调用回调函数处理
return 0;
}
三、CMakeLists文件编写(不会的小伙伴可以参考ROS学习(3)——CMakeLists文件的编写)
cmake_minimum_required(VERSION 3.0.2)
project(hello)
## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES hello
# CATKIN_DEPENDS roscpp rospy std_msgs
# DEPENDS system_lib
)
## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
add_executable(publish_man src/publish_man.cpp)
add_executable(subscrib_man src/subscrib_man.cpp)
## Specify libraries to link a library or executable target against
target_link_libraries(publish_man
${catkin_LIBRARIES}
)
target_link_libraries(subscrib_man
${catkin_LIBRARIES}
)
四、编写launch文件(不会的小伙伴可以参考ROS学习(4)——launch文件的编写)
<launch>
<node pkg="hello" type="publish_man" name="pub" output="screen" />
<node pkg = "hello" type = "subscrib_man" name = "sub" output = "screen" />
</launch>
五、在终端运行launch文件:
source ./devel/setup.bash
roslaunch <功能包名> <launch文件名.launch>
得到运行结果如下:
实现了发布消息和接收消息的过程。
需要注意的几点:
1.main函数自动补齐的格式是
int main(int argc, char const *argv[]){}
我们需要把里面的const给删掉。
2.第一条数据丢失
原因是发送第一条数据时,publisher还没有在roscore注册完毕。
解决方法是,注册后,加入休眠 ros::Duration(3.0).sleep(); 延迟第一条数据的发送。
3.出现错误ros/ros.h No such file or directory
检查CMakeLists里面是否有重复的部分,将其删除。