dbus通讯与接口介绍

 DBUS是一种高级的进程间通讯机制。DBUS支持进程间一对一和多对多的对等通讯,在多对多的通信时,须要后台进程的角色去分转消息,当一个进程发消息给另一个进程时,先发消息到后台进程,再经过后台进程将信息转发到目的进程。DBUS后台进程充当着一个路由器的角色。node

    DBUS中主要概念为总线,链接到总线的进程可经过总线接收或传递消息,总线收到消息时,根据不一样的消息类型进行不一样的处理。DBUS中消息分为四类:linux

    1.  Methodcall消息:将触发一个函数调用 ;算法

    2.  Methodreturn消息:触发函数调用返回的结果;api

    3.  Error消息:触发的函数调用返回一个异常 ;网络

    4.  Signal消息:通知,能够看做为事件消息。session

1.2  DBUS应用场景

 

    根据DBUS消息类型可知,DBUS提供一种高效的进程间通讯机制,主要用于进程间函数调用以及进程间信号广播。数据结构

1 . 函数调用多线程

    DBUS能够实现进程间函数调用,进程A发送函数调用的请求(Methodcall消息),通过总线转发至进程B。进程B将应答函数返回值(Method return消息)或者错误消息(Error消息)。架构

2 . 消息广播并发

    进程间消息广播(Signal消息)不须要响应,接收方须要向总线注册感兴趣的消息类型,当总线接收到“Signal消息”类型的消息时,会将消息转发至但愿接收的进程。

1.3  DBUS通讯特色

    DBUS是一种低延迟、低开销、高可用性的进程间通讯机制。其协议是二进制的,避免序列化的过程,通讯效率较高。DUBUS能够提供一些更高层的功能:

    1.  结构化的名字空间;

    2.  独立于架构的数据格式;

    3.  支持消息中的大部分通用数据元素;

    4.  带有异常处理的通用远程调用接口;

    5.  支持广播类型的通讯。
 

2. 技术实现

2.1 实现原理

    DBUS是一种高级的IPC通讯机制,通讯流程如图 2‑1所示。在DBUS通讯过程当中,存在一个后台进程(BUS Daemon Process)。后台进程和普通进程间信息交互是经过域套接字进行通讯。

 
图 2-1 DBUS通讯原理

     如图 2‑1所示,进程1(Process1)需先链接到总线(dbus_bus_get),其次构造消息(dbus_message_new_signal),而后发送消息(dbus_connection_send)到后台进程。后台进程接收消息,而后根据消息类型对消息进行不一样处理(bus_dispatch_matches)。

     进程2(Process2)接收消息前须要链接到总线,并告知总线本身但愿获得的消息类型(dbus_bus_add_match),而后等待接收消息(dbus_connection_pop_message)。进程2(Process2)收到总线转发的消息时会根据消息类型,作不一样的处理(如果信号类型则不须要发送返回值给总线)。

2.2 链接到总线

    进程间通讯前,须要链接到总线。调用dbus_bus_get函数链接进程到总线,创建进程和总线之间的链接(DBusConnection)。创建链接后,须要为这个链接注册名称,方便后面对这个链接进行操做,调用dbus_bus_request_name函数对链接进行注册名称。

    创建链接和注册名称是在程序开始时执行,程序结束时,调用dbus_connection_close函数关闭一个链接。函数接口声明如程序清单 2‑1所示。

程序清单 2-1 创建、注册名称和关闭链接

 

[plain]  view plain  copy
 
  1. DBusConnection  *dbus_bus_get  (DBusBusType  type,  DBusError   *error)             /*  创建和总线的链接  */  
  2.   
  3. int  dbus_bus_request_name  (DBusConnection   *connection,  
  4.                              const char         *name,  
  5.                              unsigned int        flags,  
  6.                              DBusError        *error)                                   /*  注册链接名称      */  
  7.   
  8. void  dbus_connection_close  (DBusConnection  *connection)                           /*  关闭链接          */  

 

2.3 信号发送与接收

 

2.3.1 信号发送

    DBUS中信号是一种广播的消息,当发出一个信号,全部链接到 DBUS 总线上并注册了接受对应信号的进程,都会收到该信号。

    进程发出一个信号前,须要建立一个 DBusMessage 对象来表明信号,而后追加上一些须要发出的参数,就能够发向总线了。发完以后须要释放消息对象。信号发送的函数声明如程序清单 2‑2所示。
程序清单2-2  信号发送接口
[cpp]  view plain  copy
 
  1. DBusMessage  *dbus_message_new_signal  (const  char  *path,  
  2.                                        const  char  *iface,  
  3.                                        const  char  *name)                       /*  建立信号类型消息      */  
  4.   
  5. void  dbus_message_iter_init_append  ( DBusMessage     *message,  
  6.                            DBusMessageIter  *iter)                /*  加入参数到信号        */  
  7.   
  8. dbus_bool_t  dbus_connection_send  ( DBusConnection  *connection,  
  9.                                      DBusMessage    *message,  
  10.                                      dbus_uint32_t    *serial)                       /*  发送信号到总线        */  
  11.   
  12. void  dbus_message_unref  (DBusMessage *message)                                 /*  释放消息              */  

2.3.2 信号接收

    进程接收信号时,需先告知总线进程感兴趣的消息,而后等待接收消息。信号接收函数声明如程序清单 2‑3所示。
程序清单 2-3 信号接收接口
[cpp]  view plain  copy
 
  1. void  dbus_bus_add_match  ( DBusConnection  *connection,  
  2.                             const char        *rule,  
  3.                             DBusError       *error)                                 /*  告知总线感兴趣的消息   */  
  4.   
  5. DBusMessage  *dbus_connection_pop_message  ( DBusConnection  *connection)         /*  接收消息               */  
  6.   
  7. dbus_bool_t  dbus_message_is_signal  (DBusMessage  *message,  
  8.                                       const char      *iface,  
  9.                                       const char     *signal_name)                    /*  判断消息是否为信号     */  

2.4 函数调用和提供函数调用

2.4.1 函数调用

     调用一个远程函数与发送一个信号原理相似,须要先建立一个消息(DBusMessage),而后经过注册在 DBUS上的名称指定发送的对象。而后追加相应的参数,调用方法分为两种,一种是阻塞式的,另外一种为异步调用。异步调用的时候会获得一个“DBusMessage *” 类型的返回消息,从这个返回消息中能够获取一些返回的参数。

    函数调用的函数声明如程序清单 2‑4所示。
程序清单 2-4 函数调用接口
[cpp]  view plain  copy
 
  1. DBusMessage  *dbus_message_new_method_call  (const char  *destination,  
  2.                                              const char  *path,  
  3.                                              const char  *iface,  
  4.                                              const char  *method)                    /*  建立一个函数调用消息    */  
  5.   
  6. void  dbus_message_iter_init_append  (DBusMessage     *message,  
  7.                           DBusMessageIter  *iter)                     /*  为消息添加参数           */  
  8.   
  9. dbus_bool_t  dbus_connection_send_with_reply  (DBusConnection   *connection,  
  10.                                                DBusMessage      *message,   
  11.                                                DBusPendingCall  **pending_return,  
  12.                                                int            timeout_milliseconds)       /*  发送消息                */  
  13.   
  14. void  dbus_pending_call_block  (DBusPendingCall  *pending)                           /*  阻塞等待返回值           */  
  15.   
  16. DBusMessage  *dbus_pending_call_steal_reply  (DBusPendingCall  *pending)             /*  得到返回消息            */  
  17.     
  18. dbus_bool_t  dbus_message_iter_init  (DBusMessage     *message,  
  19.                           DBusMessageIter  *iter)                     /*  获取参数                */  

2.4.2 接收函数调用

    提供远程函数调用,首先需告知总线进程感兴趣的消息,其次从总线获取消息并断定消息是方法调用。而后从消息中获取参数进行函数执行,最后建立返回消息,并发送消息至总线,由总线转发至调用的进程。函数声明如程序清单 2‑5所示。
程序清单 2-5 接收函数调用接口
[cpp]  view plain  copy
 
  1. void  dbus_bus_add_match  ( DBusConnection  *connection,  
  2.                             const char        *rule,  
  3.                             DBusError       *error)                                   /*  请求获取调用消息       */  
  4.    
  5. DBusMessage  *dbus_connection_pop_message  ( DBusConnection  *connection)           /*  从总线获取消息         */  
  6.   
  7. dbus_bool_t  dbus_message_is_method_call (DBusMessage  *message,  
  8.                                           const char     *iface,  
  9.                                           const char     *method)                       /*  断定消息是方法调用     */  
  10.     
  11. dbus_bool_t  dbus_message_iter_init  (DBusMessage     *message,  
  12.                           DBusMessageIter  *iter)                    /*  获取参数               */  
  13.   
  14. DBusMessage  *dbus_message_new_method_return  (DBusMessage *method_call)            /*  建立返回消息           */  
  15.   
  16. void  dbus_message_iter_init_append  ( DBusMessage     *message,  
  17.                            DBusMessageIter  *iter)                   /*  在消息中填入参数       */  
  18.   
  19. dbus_bool_t  dbus_connection_send  ( DBusConnection   *connection,  
  20.                                      DBusMessage     *message,  
  21.                                      dbus_uint32_t     *serial)                        /*  发送返回消息          */  

3. 小结

    DBUS是一种高效、易用的进程间通讯方式。本文档介绍了DBUS的通讯原理,以信号收发和方法调用为框架,介绍了DBUS中经常使用的函数接口。

 DBus分为两种类型:system bus(系统总线),用于系统(Linux)和用户程序之间进行通讯和消息的传递;session bus(回话总线),用于桌面(GNOME, KDE等)用户程序之间进行通讯。  

 

上节补存:

Name: 图模型中的Name 在ROS的封装体系中很是重要,全部的resource(从node到topic到service和 parameter等)都是在某个namespace中用特定的Name进行了定义。 通常来讲,resource 能够在本身的namespace中创 建新的resource,访问和使用已有的resource。连接能够创建在不一样的资源之间,namespace保证了不一样resource间的Name 不会冲突,也封装了resource内部。

 

(能够参考C++中namespace的概念。由于ROS分布式系统的设计思路,对与不一样主机上,不一样功能区块,均可以用namespace对其中 的resource name 包括 topic name 等 进行保护,使得name管理更加结构化,更多体如今对于代码重用性的提升)

 

Computation graph: Computation graph是一个p2p的网络结构。Node之间的连接关系的拓扑结构为 mesh(网状)

 

Node:node是一个处理计算的进程。(操做系统中的一个进程,由于要占用端口号进行通信,多机分布式系统中还要标明ip,详见后面的 uri)Node在graph中使用topic service和parameter相互通信,协调工做。在不一样的系统设计中,node承载的功能粒度也 不同。好比大部分的设备驱动都是单独占用一个node,为了提升代码复用率常常会有细粒度的划分,而在不少视觉处理的系统避免使用多节点结构,传递图像 数据会形成系统资源浪费(nodelet的应用)。Node的使用极大的增长了系统模块化和代码封装的程度,也给系统带来了一些错误容忍的能力。

 

Master node: master node 给ros系统中其余节点提供命名与注册的服务。它跟踪节点中的publisher与 subscriber,service 的server与client,记录其余节点的位置(uri标明,host与port),并将这些信息通知给须要 创建连接的节点。(从分布式系统的角度分析,ros这样p2p网络中集中式管理peer信息,也为仿真环境提供虚拟时钟 /clock 重要:ROS多机系统中,ros的全局时钟仅是本机的系统时钟,多机须要同步系统时间的工具来实现ros内时钟同步,通常是ntp,pr2应用 linux中的chrony进行基于ntp的系统时间同步)

 

Message: node之间通信规定的统一数据格式,ros内部有数据格式规定语言来定义,而后由相应语言的client library中的message_generation 组件生成目标代码。提供统一的串行化/解串行化 方法。

 

Topic:ros中广为使用的是异步的 publish-subscribe 通信模式。这种方式将信息的产生和使用双方解耦。通常来讲,节点没 有通信对方那边的信息。Node从须要的topic那取得消息,topic 能够有多个 subscriber 与publisher。Topic 通常 用于单向,消息流通信。Node 须要同步通信交换信息时通常使用service。Topic 通常拥有很强的类型定义:一种类型的topic只能接受/ 发送特定数据类型(message type)的message。Publisher 没有被要求类型一致性,可是接受时subscriber会检查类型 的md5,进而报错。

 

Service: service 用于处理ros通信中的同步通信,采用server/client 语义。每一个service type拥 有 request 与 response两部分,对于service中的 server,ros不会检查重名(name conflict),只有最后 注册的server会生效,与client创建链接。

 

Parameter: parameter 能够看做为ros系统运行时中定义的全局变量,而master node 中有 parameter server 来维护这些变量。而namespace的存在使得parameter 拥有了很是清晰的层次划分,避免了重名,并且使 得parameter访问能够单独访问也能够树状访问(层层解析namespace)

URI:定位node在分布式系统中的位置,格式为: protocol://host:port,protocol通常为http或者 rosrpc, host为 hostname 或者 ip 地址,port则为端口号。

TCPROS:基于tcp协议的ros应用层数据协议,用于解析topic 与 service的二进制数据流。

 

  • 远程过程调用(RPC)

ROS 通信中,节点经过远程过程调用来实现创建链接,传输数据。ROS 中远程过程调用采用XML-RPC 实现。远程调用负责管理节点对计算图 中信息的获取与更改,还有一些全局的设置。RPC不直接支持数据的流传输(经过TCPROS与 UDPROS支持)。XML-RPC 优点在于支持它的语 言类型不少,而XML自己的文本属性致使方便人调试。XML-RPC被封装在http协议中传输,XML-RPC 调用时无状态的 (stateless),没有状态信息须要追踪,简化了控制逻辑。

1》数据类型:

在XML-RPC中,方法参数和其返回值被封装在value 的实体中,value有如下几种固定的类型能够选择。(xml中体现为value的子标签)

string 为ascii 码的字符串,为value的默认格式。不合法的字符只有& 和 <,s & <;表示。

int 或者 i4 是32位的有符号整型,十进制表示中,前缀-号则为负数。

boolean 只有两种取值,0和1,用于表示布尔类型。

double  实数类型,前缀-号表示负数。

dataTime.iso8601 日期时间,用iso-8601格式表示。

base64 用 base64算法编码的二进制字符串。

array values组成的表,在data实体下一层

Struct 又称为map 表示关系的集合,每个struct实体是由一个 name 与 value的键值对组成。

2》 请求与应答:

远程过程调用由两个阶段组成:请求(request) 与 应答(response)。 一个调用者将方法调用请求发给 被调用者,而后被调用者返回调用是否成功与相应返回值。

  1. 链接模式:

下面将简述节点与其余节点如何进行链接,最后初始化一个topic data的流传输,service 的实现有些许不一样。

在以前对于节点与计算图的介绍中,节点在master node处注册 本身在publish /subscribe topic。经过 registerPublisher()和 registerSubscriber() 。节点在master处注册完本身的 subscribe/publish topic后,master都会返回一个成功的应答,其中包含全部publisher节点的URI,然 后 subscriber去与相应的publisher创建链接传输 topic 相关信息(topic name,树蕨类型,传输类型等)。有任何新的 节点去publisher 一个topic而且在master处已经完成本身的注册时,master会给全部subscribe 这个topic的节点发 出一个publisherUpdate() 请求,里面包含全部可用的 publisher的URI,topic 消息的数据由TCPROS协议传输。

简单来讲就是各个节点在 master处注册信息,master发现有节点subscribe/publish相同的topic时,将 publisher的 信息经过 RPC 分发给各个subscriber,subscriber与publisher创建第一次链接,传输topic信 息,而后再根据publisher返回的topic 信息,创建第二次链接,publisher开始 传输具体的数据。

service

 

而对称的过程是 注销(unregisteration), publisher 经过远程调用unregisterPublisher(), 然 后subscriber经过unregisterSubscriber()注销,然而,是否关闭publisher与 subscriber间的数据流传 输取决于节点自己。

Service的工做原理与topic有些许不一样,同一个service能被多个节点注册,可是只有最后一个能被其余节点接受。一个节点调用 service时,经过lookupService() 远程调用在master处查找相应service的URI。而后它将经过一个request 消 息调用service的提供者,若是成功了,service提供者将返回一个相应的response 消息,失败了返回相应错误消息,(全部的消息传输默 认都是经过 TCPROS协议。)

subscribe_publisher

 

  • 数据流

XML-RPC为咱们提供了方便整洁的远程调用协议,可是它的冗长与以文本为中心的编码格式使得它不适合高带宽,低延迟的数据传输任务。ROS定义 了本身的二进制数据流传输协议,减小了冗余的协议增长从而增长带宽,协议设计使得数据几乎不须要解析(相对于rpc),从而减小延迟。详细的TCPROS 协议内容能够在 wiki.ros.org/ROS/TCPROS 找到。

(如今ROS中也有实现的UDPROS协议,能够经过TransportHints 数据结构指定下层传输协议,甚至经过继承Trasport 类 本身实现本机的进程间通信方法)。

下面捡一些TCPROS中的重点说一下:

Md5:TCPROS为了保证两边传输数据类型一致,会在协议头中给出topic name 的md5 hash算法处理过的值,而每次你生成新的msg 时,md5的值都会由于你内部数据类型的变更而改变,这样就避免了新msg与 旧msg 传输类型不一致的问题。

Subscriber 选项tcp_nodelay :若是是“1” 则给socket 设置 TCP_NODELAY 选项,下降延迟,可能会下降传输效率。

Service client 选项:persistent 设置为1,则service的连接会一直开放给多个 service request

(下面是一些理解:

  • TCPROS的协议头占的字节数比较固定,因此传输一帧中只传输有效位几个字节是很是不划算的,不少状况下能够附上 std_msgs/header ,seq能够检测丢包,stamp能够检测消息实时性,frame_id不少状况下必备。
  • Roscpp 对 数据流的控制api比rospy要丰富不少,好比roscpp中有对callback queue的操做,多线程回调函数的支持等,对数据传输要求比较高的节点仍是老老实实用cpp吧
  • 在一些对延迟要求比较高而又有一些无线通信等高延迟传输介质存在的应用中,能够考虑用低延迟的方式互联,好比xbee模块代替wifi(本身写一些与其余节点的bridge),或者udpros代替tcpros,而且避免tcpros的协议头占用过多带宽)
相关文章
相关标签/搜索