ROS学习总结——节点之间的通信方式及其实现(C++)

节点(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名称服务。

发布者和订阅者建立连接之前的准备

  1. 发布者(Publisher)节点运行后会使用XML/RPC向 ROS Master 注册发布者的相关信息,包括节点名称、话题名称、消息类型、话题缓存大小等,Master会将这些信息存入注册列表中。
  2. 订阅者(Subscriber)节点运行后会使用XML/RPC向 ROS Master 注册订阅者的相关信息,包括节点名称、话题名称、消息类型、话题缓存大小等,Master会将这些信息存入注册列表中。
  3. Master会根据订阅者提供的相关信息,在注册列表中寻找匹配的发布者;如果找到合适的发布者,则将发布者的地址发送给订阅者;如果没有找到匹配的发布者则会一直等待,直到有合适的发布者注册。
  4. 在订阅者接收到Master的回应后,会根据接收到的发布者信息,使用XML/RPC请求与发布者之间直接进行连接;
  5. 发布者接收到订阅者的信息后,会使用XML/RPC方式将TCP服务器的URI地址和端口作为连接响应发送给订阅者节点。

发布者和订阅者建立连接

  1. 订阅者在接收到发布者返回的确认消息后,将会使用TCPROS创建一盒与发布者节点对应的客户端,并直接与发布者节点连接。
  2. 成功建立连接后,发布者将会使用TCPROS向订阅者发布消息。

使用C++构建订阅者和发布者节点

创建msg文件

msg是一个简单的文本文件,描述了ROS message的变量(fileds、数据)。ROS会根据msg文件中定义的内容自动生成不同语言的消息源码

msg文件允许的变量类型
int8, int16, int32, int64 (plus uint*)
float32, float64
string
time, duration
other msg files
variable-length array[] and fixed-length array[C]

第一步:在Package内创建msg文件夹,用于存放编写的msg文件

$ 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则是运行时的依赖。

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

第四步:修改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<beginner_tutorials::message>("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 = "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连接相同,但是与话题不同的是,服务只连接一次,再执行请求和响应之后彼此断开连接。如果有必要,需要重新进行连接。

服务器和客户端通信的过程

  1. 服务器Node B提供了一个服务的接口/Service
  2. Node A通过/Service发送数据给Node B即“请求”,同时等待服务器Node B返回应答数据。
  3. 服务器返回了应答数据后,客户端Node A与服务器Node B断开连接继续执行。
    在这里插入图片描述

使用C++构建一个服务器和客户端

创建srv文件

srv 文件与 msgmsg 文件类似,只是 srv 文件描述了serivces的数据;该数据两个部分:request和 response。两个部分用‘---’分割。
①首先beginner_tutorials包目录下建立一个叫做srv的子目录,并创建一个AddTwoInts.srv的文件。文件的内容如下
AddTwoInts.srv
上图中a和b使request,sum是response;
②为了使其能够生成C++,python源码,我们需要对package.xml以及CMakeLists.txt文件进行如下修改
对于package.xml文件同样需要添加下面两行代码,关于这两行代码的解释在上面创建msg文件的时候已经说明

<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>

找到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 <cstdlib>									//调用该头文件用来使用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<beginner_tutorials::AddTwoInts>("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

<?xml version="1.0"?>
<package format="2">
  <name>beginner_tutorials</name>
  <version>0.0.0</version>
  <description>The beginner_tutorials package</description>

  <maintainer email="long@todo.todo">long</maintainer>

  <license>TODO</license>

  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>message_generation</build_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  <exec_depend>message_runtime</exec_depend>

</package>

文章内容包含个人理解,如有错误欢迎指出,我会及时纠正。

  • 13
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ROS2中,C++节点通信主要通过ROS2提供的rclcpp库实现。rclcpp库是一个ROS2的C++客户端库,它提供了创建ROS2节点、发布和订阅话题、服务调用等功能。 下面是一个简单的例子,演示如何使用rclcpp库在C++节点之间进行通信: 1. 首先,我们需要包含必要的头文件: ```cpp #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" ``` 2. 创建一个发布者: ```cpp auto publisher = node->create_publisher<std_msgs::msg::String>("my_topic", 10); ``` 其中,`node`是一个`rclcpp::Node`对象,`std_msgs::msg::String`是消息类型,`my_topic`是话题名称,`10`是话题队列长度。 3. 创建一个消息: ```cpp auto message = std_msgs::msg::String(); message.data = "Hello, world!"; ``` 4. 发布消息: ```cpp publisher->publish(message); ``` 5. 创建一个订阅者: ```cpp auto subscription = node->create_subscription<std_msgs::msg::String>("my_topic", 10, [](const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "I heard: '%s'", msg->data.c_str()); }); ``` 其中,`std_msgs::msg::String`是消息类型,`my_topic`是话题名称,`10`是话题队列长度,`[](const std_msgs::msg::String::SharedPtr msg) {...}`是消息回调函数,用于处理接收到的消息。 6. 运行节点: ```cpp rclcpp::spin(node); ``` 以上是一个简单的例子,演示了如何在C++节点之间使用ROS2进行通信。在实际应用中,我们可以根据需要创建多个发布者和订阅者,以实现节点之间的复杂通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值