0%

ROS基础:通讯和回调函数

通讯

ROS将每个进程看做一个节点,使用节点管理器进行统一管理,并提供一套响应的消息传递机制。在ROS中,所有的消息通信都必须使用节点管理器。ROS的特殊性主要体现在消息通讯层,而不是更深的层次。点对点的连接和配置通过XMLRPC机制实现;节点间的数据流通过网络套接字实现,数据流在ROS中被称为消息,模块间的消息传递采用简单的、语言无关的接口定义描述。

ROS的底层的通信是通过HTTP完成的,因此ROS内核本质上是有一个HTTP服务器,它的地址一般是 http://localhost:11311/,即本地的11311端口,当需要连接到另一台计算机上运行的ROS时,只要连上该机的11311端口即可。

ROS提供多种类型的通讯机制,包括基于主题的异步数据流通信、基于服务的同步RPC(远程过程调用)通信以及基于参数服务的数据传递,支持实现不同需求的网络连接。

基于主题的异步数据流通信

ROS中最主要的通信方式,它实现了节点间多对多的连接,并且采用单向数据流传输数据。通过主题可以实现发布者和订阅者之间的解耦。底层依赖于XMLRPC和TCP/UDP。步骤如下:

  1. talker注册:使用RPC向master注册发布者的信息,包含发布消息的话题名;master将注册信息加入注册列表中;
  2. listener注册:使用RPC向master注册订阅者的信息,包含需要订阅的话题名;
  3. master信息匹配:根据listener的订阅信息查询注册列表,如果没找到,则等待发布者加入;如果找到了,通过RPC向listener发布talker的RPC地址;
  4. listener发送连接请求:listener收到talker的RPC地址之后,尝试通过RPC向talker发送连接请求,传输订阅的话题名,消息类型以及通讯协议(TCP/UDP)
  5. talker确认连接请求:talker收到listener发送的连接请求后,继续通过RPC向listener确认连接信息,其中包含自身的TCP地址信息;
  6. listener与talker建立网络连接:尝试使用TCP或UDP建立网络连接;
  7. talker向listener发布数据:成功建立连接之后,talker开始向listener发布数据;

步骤1-5,使用的都是XMLRPC通信,其中步骤4和5,talker和listener会协商共同支持的协议(TCPROS或UDPROS),最后6-7才是真正的TCP或UDP通讯。

XMLRPC:ROS节点之间建立连接过程及其与master之间通信均使用XMLRPC(基于XML的远程过程调用),这些远程过程调用采用HTTP传输协议,XML作为传输数据的编码格式。XML的文本属性使得它独立于传输层的编码形式,直接封装在HTTP中传输。远程过程调用的主要作用是负责节点对计算图级中的信息进行获取、更改及一些全局变量的设置,不直接支持数据的流传输。

TCPROS和UDPROS:ROS主题通讯支持TCPROS和UDPROS两种通信协议,TCPROS采用标准的TCP/IP套接字,这是ROS默认的传输方式;UDPROS采用标准的UDP传输方式,它是一种低延时、高效率的传输方式,但可能产生数据丢失,所以它比较适用于远程操作任务。

基于服务的同步RPC通信

可以实现一对一的连接,采用请求应答的模型。步骤如下:

  1. talker注册:使用RPC向master注册,包含提供的服务名;master将注册信息加入注册列表中;
  2. listener注册:同样通过RPC向master注册,包含需要查找的服务名;
  3. master信息匹配:master查询注册列表,没找到,则等待服务加入;找到的话,则通过RPC向listener发送talker的TCP地址
  4. listener与talker建立网络连接:listener使用tcp尝试与talker建立网络连接,并发送服务请求数据;
  5. talker向listener发布数据:talker收到服务请求和参数之后,开始执行服务功能,执行完成后,向listener发送应答数据。

可以看到,相比基于主题的异步数据流通信,该过程少了双方协商协议的过程,因为同步RPC通讯只支持TCPROS,客户端不需要通过XMLRPC与服务端协商共同支持的协议。

参数服务器

参数服务器是一个可以通过网络访问的多元共享的参数字典,节点在运行时可以在参数服务器上存储或获取参数。参数服务器运行在节点管理器上,它的应用程序编程接口通过XMLRPC库进行访问。可以理解成ROS中的全局变量,只需要XMLRPC通讯,不用TCP,操作如下:

  1. talker设置参数
  2. listener查询参数
  3. master将查询结果返回给listener

动作action通讯

带有响应机制的服务。相比于服务的阻塞特性,动作可以在服务端任务执行的过程中,反馈给客户端任务执行的状态,便于客户端查看进度并根据反馈结果作出合理响应。

优缺点和注意事项

整体来讲,topic适合数据传输,service适合逻辑处理,action用在需要监视的场景,如实际机器人的轨迹执行。一般使用时都不会有问题,毕竟ROS还是一个很好的工具。但是如果数据量比较大或者回调函数运行时间很长,延时一般就不能忽略,此时就涉及到通讯中的回调函数的处理。

回调函数和多线程

在基于服务的通信中,客户端和服务端的连接每次都会经历一个查找和连接的过程,而且连接仅在此次服务调用期间有效,在调用返回后将被关闭。服务端回调函数每次只处理客户端发送的数据,并不需要维持一个回调函数队列,而且服务端和客户端的api中并没有回调函数队列长度参数。

但是在基于主题的异步数据流通讯中,订阅者无法确定消息的到达时间,所以利用回调函数,把响应收到消息事件的代码放到回调函数里。通过轮转函数实现回调函数的调用,从而实现消息处理。

ROS提供了三种形式的回调函数队列,分别是:

  1. 每个订阅者的回调函数队列
  2. 每个NodeHandle的回调函数队列
  3. 全局回调函数队列,即节点的回调函数队列

其中全局回调函数队列在创建节点时自动创建,前两种需要手动创建。当消息到达时,先查看订阅者回调队列,在查看NodeHandle回调队列,最后才查看全局回调队列。

单线程轮询

ros::spin()会导致用户编写的所有回调函数都被调用,并且一旦被调用,将不会返回,除非被关闭。使用ros::spin时,一般在初始化时,都设置好了所有消息的回调函数,并且不需要其他辅助程序运行。

ros::spinOnce调用后会及时返回,并且还可以继续执行之后的程序。它会在某个时间段内及时调用所有的回调函数。一般还需要其他辅助程序的执行,如定时任务,数据处理,用户界面等。

多线程轮询

ros::MulitiThreadSpinner,阻塞执行轮转,与ros::spin类似,执行后无需返回,直到节点被关闭。

ros::AsyncSpinner: 没有使用阻塞形式,而是包含了start和stop调用,这样编程人员可以随时打开或者停止在回调函数队列上的轮转,实际使用中,这个更有用。