ROS通信机制
Ros是进程的分布式框架。这些进程可分布于不同的主机,不同主机协同工作,以分散计算压力。
不同的进程是如何进行通信的呢?(如何实现数据交换)这就需要通信机制了。
- 话题通信(发布订阅模式)
- 服务通信(请求相应)
- 参数服务器(参数共享)
话题通信
- Ros Master管理者
- Talker发布者
- Listerner订阅者

实现步骤:
- Talker注册
Talker 启动后,会通过 RPC 在 ROS Master 中注册自身信息,其中包含所发布消息的话题名称。 ROS Master 会将节点的注册信息加入到注册表中。
- Listener注册
Listener 启动后,也会通过 RPC 在 ROS Master 中注册自身信息,包含需要订阅消息的话题名。 ROS Master 会将节点的注册信息加入到注册表中。
- ROS Master 实现信息匹配
- Listener向 Talker 发送请求
Listener 根据接收到的 RPC 地址,通过 RPC 向 Talker 发送连接请求,传输订阅的话题名称、消息类型以及通信协议( TCP / UDP )。
- Talker确认请求
Talker 接收到 Listener 的请求后,也是通过 RPC 向 Listener 确认连接信息,并发送自身的TCP地址信息。
- Listener与 Talker 建立连接
Listener 根据步骤4返回的消息使用 TCP 与 Talker 建立网络连接。
- Talker向 Listener 发送消息
连接建立后, Talker 开始向 Listener 发布消息。
注意
注意1:上述实现流程中,前五步使用的 RPC 协议,最后两步使用的是 TCP 协议
注意2:Talker与 Listener 的启动无先后顺序要求注意3:Talker与 Listener 都可以有多个
注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master 。也即,即便关闭 ROS Master , Talker 与 Listern 照常通信。
实现
需求:
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。
分析:
在模型实现中, ROS master 不需要实现,而连接的建立也已经被封装了,需要关 关键点有三个:
1.发布方2.接收方
3.数据(此处为普通文本)流程:
1.编写发布方实现;2.编写订阅方实现;
3.编辑配置文件:
4.编译并执行。
发送方
1、话题通信 ROS
(1)编写发布方实现:
需求:实现基本的话题通信,一方发布数据,一方接收数据,
实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)
二者需要设置相同的话题消息发布方:
循环发布信息: HelloWorld 后缀数字编号实现流程:
1.包含头文件
2.初始化 ROS 节点:命名(唯一)
3.实例化 ROS 句柄
4.实例化发布者对象
5.组织被发布的数据,并编写逻辑发布数据
talker.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream>
int main(int argc, char *argv[]) { setlocale(LC_ALL,"");
ros::init(argc,argv,"talker"); ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
std_msgs::String msg; std::string msg_front = "Hello 你好!"; int count = 0;
ros::Rate r(1);
while (ros::ok()) { std::stringstream ss; ss << msg_front << count; msg.data = ss.str(); pub.publish(msg); ROS_INFO("发送的消息:%s",msg.data.c_str());
r.sleep(); count++; ros::spinOnce(); }
return 0; }
|
订阅方
(2)编写订阅方实现:
需求:实现基本的话题通信,一方发布数据,一方接收数据,实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)消息订阅方:
订阅话题并打印接收到的消息实现流程:
1.包含头文件
2.初始化 ROS 节点:命名(唯一)
3.实例化 ROS 句柄
4.实例化订阅者对象
5.处理订阅的消息(回调函数)
6.设置循环调用回调函数
listener.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
#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());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"listener");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
ros::spin();
return 0;
}
|
配置CmakeLists.txt文件
1 2 3 4 5 6 7 8 9 10
| # add_executable(${PROJECT_NAME}_node src/talkerToListener_node.cpp) add_executable(talker src/talker.cpp) add_executable(listener src/listenner.cpp)
target_link_libraries(talker ${catkin_LIBRARIES} ) target_link_libraries(listener ${catkin_LIBRARIES} )
|
运行
roscore

source ./devel/setup.bash
rosrun talkerToListener talker

话题通信自定义 msg
定义 msg 文件
在功能包下新建 msg 目录,添加文件 Person . msg
1 2 3
| string name uint16 age float64 height
|
编辑配置文件
在 package . xml 中添加编译依赖与执行依赖
1 2 3
| < build _ depend > message _ generation </ build _ depend > < exec _ depend > message _ runtime </ exec _ depend > <!--exce _ depend 以前对应的是 run _ depend 现在非法-->
|
在CMakeLists.txt中编辑 msg 相关配置
tips:(Ctrl+F快速搜索)
1 2 3 4 5 6
| find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation )
|
配置 msg 源文件
1 2 3 4 5
| add_message_files( FILES Person.msg )
|
生成消息时依赖于 std_msgs
1 2 3 4 5
| generate_messages( DEPENDENCIES std_msgs )
|
执行时依赖
1 2 3 4 5 6 7 8
| catkin_package( # INCLUDE_DIRS include # LIBRARIES demo02_talker_listener CATKIN_DEPENDS roscpp rospy std_msgs message_runtime # 只要这行,后面加上message_runtime就行 # DEPENDS system_lib )
|
编译
Ctrl+Shift+B编译
可以看到C++需要调用的中间文件:

py文件需要的:

话题通信自定义msg调用A(C++)
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出
- 编写发布方实现;
- 编写订阅方实现;
- 编辑配置文件;
- 编译并执行。
vscode配置
c_cpp_properties.json配置文件默认是不会产生的,ctrl+shift+p 再输入configuration选择后便会出现。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| { "configurations": [ { "browse": { "databaseFilename": "${default}", "limitSymbolsToIncludedHeaders": false }, "includePath": [ "/opt/ros/noetic/include/**", "/usr/include/**", "/home/kjjj/demo03_ws/devel/include/**"//配置head文件的路径 ], "name": "ROS", "intelliSenseMode": "gcc-x64", "compilerPath": "/usr/bin/gcc", "cStandard": "gnu11", "cppStandard": "c++14" } ], "version": 4 }
|
发布方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
#include "ros/ros.h" #include "/home/kjjj/demo03_ws/devel/include/talkerToListener/Person.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,"");
ros::init(argc,argv,"person_talker");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<talkerToListener::Person>("chatter_person",1000);
talkerToListener::Person p; p.name = "sunwukong"; p.age = 2000; p.height = 1.45;
ros::Rate r(1); while (ros::ok()) { pub.publish(p); p.age += 1; ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);
r.sleep(); ros::spinOnce(); }
return 0; }
|
订阅方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
#include "ros/ros.h" #include "/home/kjjj/demo03_ws/devel/include/talkerToListener/Person.h"
void doPerson(const talkerToListener::Person::ConstPtr& person_p){ ROS_INFO("订阅的人信息:%s, %d, %.2f", person_p->name.c_str(), person_p->age, person_p->height); }
int main(int argc, char *argv[]) { setlocale(LC_ALL,"");
ros::init(argc,argv,"person_listener"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe<talkerToListener::Person>("chatter_person",10,doPerson);
ros::spin(); return 0; }
|
配置CMakeLists.txt
1 2
| add_executable(person_talker src/person_talker.cpp) add_executable(person_listener src/person_listener.cpp)
|
1 2
| add_dependencies(person_talker ${PROJECT_NAME}_generate_messages_cpp) add_dependencies(person_listener ${PROJECT_NAME}_generate_messages_cpp)
|
注意这里应该在add_executable之后

如果出现错误:

这里是因为你修改的时候改错了,在add_executable之上也有add_dependencise()

运行成功
至此可以在终端运行,方法如前文所述:
