ROS学习总结——节点之间的两种基本的通信方式实现(C++)
- 节点(Nodes)
-
- 节点相关指令
- 通信方式——话题(Topic)
-
- 话题通信机制详解
-
- 几个关键字的解释
- 发布者和订阅者建立连接之前的准备
- 发布者和订阅者建立连接
- 使用C++构建订阅者和发布者节点
-
- 创建msg文件
- 创建发布者(Publisher)
- 创建订阅者(Subscriber)
- 编译节点
- 话题相关命令
- 通信方式——服务(Services)
-
- 服务通信机制详解
-
- 服务器和客户端建立连接之前的准备
- 服务器和客户端通信的过程
- 使用C++构建一个服务器和客户端
-
- 创建srv文件
- 创建服务端(Service)
- 创建客户端(Client)
- 编译节点
- 服务的相关命令
- 关于CMakeLists.txt以及package文件的详细内容(去注释)
-
- CMakeLists.txt
- package.xml
节点(Nodes) 节点,一个节点即为一个可执行文件,它可以通过ROS与其他节点进行通信。
节点相关指令
$ roscore#运行所有ROS程序之前都要首先执行的指令
$ rosrun [package_name] [node_name]#运行一个节点
$ rosnode list#显示当前运行的节点
$ rosnode ping [node_name]#测试一个正在运行的节点
通信方式——话题(Topic) Topic是ROS节点之间的一种单向的通讯方式,发布者用于发布话题,同理订阅者用来订阅话题;Topic通信通常适合于对实时性有要求,需要频繁通信的场合。
一个话题可以有多个发布者,也可以有多个订阅者;两者之间的关系如下图
文章图片
话题通信机制详解
文章图片
几个关键字的解释
XML/RPC:全称是XML Remote Procedure Call,即XML远程方法调用。它是一套允许运行在不同操作系统、不同环境的程序实现基于Internet过程调用的规范和一系列的实现。这种远程过程调用使用http作为传输协议,XML作为传送信息的编码格式。发布者和订阅者建立连接之前的准备
TCPROS:ROS消息和服务的传输层。它使用标准的TCP/IP套接字来传输消息数据。入站连接通过一个包含消息数据类型和路由信息的头的TCP服务器套接字接收。
ROS Master:ROS的节点管理器,ROS名称服务。
- 发布者(Publisher)节点运行后会使用XML/RPC向 ROS Master 注册发布者的相关信息,包括节点名称、话题名称、消息类型、话题缓存大小等,Master会将这些信息存入注册列表中。
- 订阅者(Subscriber)节点运行后会使用XML/RPC向 ROS Master 注册订阅者的相关信息,包括节点名称、话题名称、消息类型、话题缓存大小等,Master会将这些信息存入注册列表中。
- Master会根据订阅者提供的相关信息,在注册列表中寻找匹配的发布者;如果找到合适的发布者,则将发布者的地址发送给订阅者;如果没有找到匹配的发布者则会一直等待,直到有合适的发布者注册。
- 在订阅者接收到Master的回应后,会根据接收到的发布者信息,使用XML/RPC请求与发布者之间直接进行连接;
- 发布者接收到订阅者的信息后,会使用XML/RPC方式将TCP服务器的URI地址和端口作为连接响应发送给订阅者节点。
- 订阅者在接收到发布者返回的确认消息后,将会使用TCPROS创建一盒与发布者节点对应的客户端,并直接与发布者节点连接。
- 成功建立连接后,发布者将会使用TCPROS向订阅者发布消息。
msg是一个简单的文本文件,描述了ROS message的变量(fileds、数据)。ROS会根据msg文件中定义的内容自动生成不同语言的消息源码
msg文件允许的变量类型第一步:在Package内创建msg文件夹,用于存放编写的msg文件
int8, int16, int32, int64 (plus uint*)
float32, float64
string
time, duration
other msg files
variable-length array[] and fixed-length array[C]
$ roscd beginner_tutorials
$ mkdir msg
第二步:编写msg文件
$ cd msg
$ echo "string data" >> message.msg
$ echo "int64 count" >> message.msg
第三步:在package.xml功能包中添加下面两行依赖
message_generation用于生成与msg文件相关的.h和.py文件。message_runtime则是运行时的依赖。
message_generation
message_runtime
第四步:修改CMakeLists.txt文件中的编译选项
①找到
find_package
添加参数message_generation
②去掉
add_message_files
的注释,将参数更改为(FILES message.msg)
③去掉
generate_messages()
函数的注释④去掉
catkin_package
的注释更改参数为CATKIN_DEPENDS message_runtime
,导入message runtime依赖第五步:生成源代码文件
$ rosmake beginner_tutorials
一切完成后会在
catkin_ws/include/beginner_tutorials/
目录下生成名为message.h的C++头文件创建发布者(Publisher)
在功能包src目录下创建一个talker.cpp文件,代码如下
#include "ros/ros.h"//该文件引用了ROS操作系统的一些常用头文件
#include "beginner_tutorials/message.h"//调用由我们定义的消息文件编译后自动生成的头文件int main(int argc,char **argv){ros::init(argc, argv, "talker");
//ROS出使化并制定节点名称,该名称可以在运行时进行重映射
ros::NodeHandle n;
//创建一个指向该线程节点的句柄,第一个创建的节点句柄会做一些节点的初始化操作
ros::Publisher chatter_pub = n.advertise("chatter", 1000);
//告知Master节点,我们要向chatter话题发布一个beginner_tutorials/message类型的消息
//将publisher队列的大小定义为1000个消息,超过大小之后将会丢弃旧的消息
ros::Rate loop_rate(10);
//定义循环频率为10Hz
int count = 0;
while(ros::ok()){缺省情况下,roscpp会安装一个SIGINT的handler,这个handler会处理Ctrl+C等异常事件,使ros::ok()返回false
beginner_tutorials::message msg;
msg.data = "https://www.it610.com/article/Hello World";
msg.count = count++;
chatter_pub.publish(msg);
//将信息广播给订阅该话题的节点
ROS_INFO("The count is: %ld.\t%s", (long int)msg.count, msg.data.c_str());
//ROS中用来代替printf/cout等函数的输出功能
//ros::spinOnce();
//在不接收任何callback的情况下,可以省略
loop_rate.sleep();
//休眠一定时间保证10Hz的发布频率
}
return 0;
}
创建订阅者(Subscriber)
在功能包src目录下创建一个listener.cpp文件,代码如下
#include "ros/ros.h"
#include "beginner_tutorials/message.h"//定义回调函数
void chatterCallback(const beginner_tutorials::message::ConstPtr& msg){
ROS_INFO("I heard:[%s]\tThe count is %ld.", msg->data.c_str(), (long int)msg->count);
}int main(int argc, char **argv){
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
//创建一个Subscriber,订阅一个名为chatter的话题,注册回调函数chatterallback
ros::spin();
//进入自循环,等待进入回调函数
return 0;
}
编译节点
在CMakeLists.txt文件末尾内添加如下语句
include_directories(include ${catkin_INCLUDE_DIRS})进入工作空间目录,使用
#设置头文件的相对路径
add_executable(talker src/talker.cpp)
#将talker.cpp文件生成为一个名为talker的可执行文件
target_link_libraries(talker ${catkin_LIBRARIES})
#将可执行文件同其所依赖的第三方库进行链接
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
catkin_make
命令编译成功过后就可以使用rosrun
指令来运行节点观看结果话题相关命令
$ rqt_graph#图形化显示ROS系统当前的运行情况
$ rqt_plot#实时显示一个发布到某个话题上的数据变化图形
$ rostopic list#列出当前运行的所有主题
$ rostopic hz [topic]#查看某一话题发布消息的频率
$ rostopic echo [topic]#查看某一话题发布的消息内容
$ rostopic type [topic]#查看某一话题发布的消息类型
$ rostopic pub [topic] [msg_type] [args]#将数据发布到某个正在广播的话题上
$ rosmsg show [package_name] [msg_type] #查看某一类型消息的详细内容,package_name参数可以省略
通信方式——服务(Services) 服务(services)是基于 C/S 模式的双向数据传输模式(有应答的通信机制),话题是无应答的通信机制。该通讯方式无较大的资源占用,适合于不频繁通信的场合。例如某个节点A向节点B提交服务申请(request),节点B执行回调函数,并将结果返回(response)给节点A。
一个服务器可以有多个客户端,但是一个客户端只能有一个服务器;
服务通信机制详解 服务器和客户端建立连接之前的准备
服务器和客户端之间的连接与上述发布者和订阅者之间的TCPROS连接相同,但是与话题不同的是,服务只连接一次,再执行请求和响应之后彼此断开连接。如果有必要,需要重新进行连接。
服务器和客户端通信的过程
- 服务器
Node B
提供了一个服务的接口/Service
。 Node A
通过/Service发送数据给Node B
即“请求”,同时等待服务器Node B
返回应答数据。- 服务器返回了应答数据后,客户端
Node A
与服务器Node B
断开连接继续执行。
文章图片
srv 文件与 msgmsg 文件类似,只是 srv 文件描述了serivces的数据;该数据两个部分:request和 response。两个部分用‘---’分割。
①首先beginner_tutorials包目录下建立一个叫做srv的子目录,并创建一个AddTwoInts.srv的文件。文件的内容如下
文章图片
上图中a和b使request,sum是response;
②为了使其能够生成C++,python源码,我们需要对package.xml以及CMakeLists.txt文件进行如下修改
对于package.xml文件同样需要添加下面两行代码,关于这两行代码的解释在上面创建msg文件的时候已经说明
message_generation
message_runtime
找到CMakeLists.xml文件中的
add_service_files()
函数,修改其参数为FILES AddTwoInts.msg
;③最后使用
catkin_make
指令对功能包进行编译,生成srv文件的源代码文件。创建服务端(Service)
在功能包的src目录下创建文件
add_two_ints_server.cpp
并添加如下代码#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h" //导入由srv文件生成的头文件
/*
*函数提供了两个ints相加的serivers。他接收request中的数据并将结果放入response中。
* */
bool add(beginner_tutorials::AddTwoInts::Request &req, \
beginner_tutorials::AddTwoInts::Response &res){
res.sum = req.a + req.b;
ROS_INFO("request: x = %ld,y = %ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv){ros::init(argc, argv, "add_two_ints_server");
//初始化ROS
ros::NodeHandle n;
//创建节点句柄
ros::ServiceServer service = n.advertiseService("add_two_ints",add);
//创建service并广播给ROS。告知有"add_tow_ints"这么一个service
ROS_INFO("Ready to add two ints.");
ros::spin();
//进入自循环,等待进入回调函数return(0);
}
创建客户端(Client)
在功能包的src目录下创建文件
add_two_ints_client.cpp
并添加如下代码#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include //调用该头文件用来使用atoll()进行字符串转换
int main(int argc, char **argv){ros::init(argc, argv, "add_two_ints_client");
//初始化ROS
if(argc != 3){
//判断输入参数数目是否正确
ROS_INFO("usage:add_two_ints_cliect X Y");
return 1;
}
ros::NodeHandle n;
//创建节点句柄
ros::ServiceClient client = n.serviceClient("add_two_ints");
//创建一个应用add_two_ints服务的client。该对象用于稍后调用服务
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if(client.call(srv)){//发送service请求,等待返回运算结果
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
//将输入的参数取出
}else{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
编译节点
在CMakeLists.txt文件末尾内添加如下语句
add_executable(add_two_ints_server src/add_two_ints_server.cpp)服务的相关命令
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
$ rosservice list#查找可用服务的信息
$ rosservice type [service]#产看指定服务类型
$ rosservice call [service] [args] #调用某一服务
$ rossrv show [service_name]#查看srv文件的内容
关于CMakeLists.txt以及package文件的详细内容(去注释) CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg message_generation)
add_message_files(FILES message.msg)
add_service_files(FILES AddTwoInts.srv)
generate_messages()
catkin_package(CATKIN_DEPENDS message_runtime)
include_directories(include ${catkin_INCLUDE_DIRS})
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
package.xml
beginner_tutorials
0.0.0
The beginner_tutorials package long TODO catkin
roscpp
rospy
std_msgs
message_generation
roscpp
rospy
std_msgs
roscpp
rospy
std_msgs
message_runtime
【ROS学习总结——节点之间的通信方式及其实现(C++)】文章内容包含个人理解,如有错误欢迎指出,我会及时纠正。