天天看点

DBus介绍

一篇关于DBus的入门介绍,澄清了一些D-Bus中容易混淆的概念

意译:freeworkzz 

日期:2010-07-21

来源:http://www.freedesktop.org/wiki/IntroductionToDBus

关于本文

本文不是教程,更不是手册。它不会教你如何使用D-Bus,也不会教你如何安装及如何写基于D-BUS的程序。

这里有的,是解释了D-Bus到底是什么,其背后的概念以及如何将这些概念集合在一起,还有一些必须了解的术语。这里没有不必要的技术细节,也并不关心读者所使用的编程语言。初学者可以在阅读其他教程或指南前先参考本文,它将告诉你使用D-Bus可以做什么。

即使读者已经有了一本好的教程,先读一读本文也是有好处的。在D-Bus的世界中,有很多专用术语,它们与通常的概念并不完全一致。这篇文章将从最基础开始解释这些术语,从而避免理解上的偏差。本文也尝试从不同角度来看D-Bus,避免只专注于一种编程语言来阐述时对使用其他语言的读者产生排斥。

D-Bus概述

D-Bus是一种本地进程间通信机制(不同主机间的进程通信支持可能会在将来加入,但这并不是D-Bus最初设计思想)。D-Bus的特点是轻量级、快速,为主流桌面环境提供统一的进程间通信界面。

与其他重量级的进程间通信技术不同,D-Bus并未使用会话进行通信。D-Bus使用了状态以及连接的概念,使其比UDP等低级的信息传输协议更“聪明”。另一方面,它传送的是离散消息,这又与TCP协议将数据看做“流”有所不同。D-Bus支持点对点的传信,以及广播/订阅式的传信两种传信方式。

D-Bus将要传输的数据结构化为二进制数据,包括不同长度的整数、浮点数、字符串、复合类型等。因为所有的传输数据都是由一定规则构成,不符合规则的信息可以简单地忽略掉。通俗地说,D-Bus就是整套进程通信机制的指挥官。

与编程语言的绑定

D-Bus在很多不同的编程语言上都有其接口实现。这些接口封装了D-Bus低级API,提供了更符合此编程语言的语法结构。

与一般的通信概念相比,使用D-Bus时应该更倾向于使用面向对象的思维方式。在一些语言中,使用者甚至很难标察觉到D-Bus的存在,他们感觉就好像是在使用语言原生组件一样(这里的“组件”指的是库、模块、包、对象、函数等)。因此,D-Bus在不同语言的实现接口差别很大,而且在一些语言中显得很简单。

实现D-Bus接口的语言正在逐渐增加。在C语言中,有最低级的API,但其实现及使用上非常复杂。C语言中另一个实用化的实现基于GLib。在Java、Perl、Python等都有D-Bus接口。

Buses(总线)

D-Bus有两个主要组成部分:一是点对点通信的支持库,任何想用D-Bus通信的进程都可以引用;二是dbus服务进程,其作用类似于总线,进程可以连接到这个总线,并在总线上传输消息。典型的使用流程是:进程使用支持库,连接到服务进程总线,并收发消息。

一个系统中可以同时存在多条D-Bus总线。D-Bus最初用来替代GNOME桌面环境中的CORBA式进程通信组件。与KDE使用的DCOP类似,D-Bus正在成为主流桌面环境甚至其他平台的标准配件。GNOME一般使用两类总线:一条系统级总线,提供类似硬件变更之类消息;另外,每个用户都有一条用户会话总线。每条会话总线仅供单独的用户使用,同时D-Bus也提供了用户权限机制供系统总线使用。

Addresses(地址)

每条总线都有总线地址,进程通过这个地址连接到总线。总线地址一般是类似于”/tmp/.hiddensocket”这类Socket接口,但也有可能是TCP端口,或者其他通信方式接口。具体如何使用这些接口通信,是完全封装在dbus支持库中。我们一般只说客户进程打开并使用到总线的连接。

设置与启动

总线服务使用dbus-launch命令启动。它使用–config-file选项指明所使用的总线设置文件。默认的设置文件位于/etc/dbus-1/system.conf与

/etc/dbus-1/session.conf,分别对应系统总线与会话总线。

设置文件使用的是XML格式。

Connections(连接)

总线上的每个连接都有一个或多个名字。这些名字一般叫做连接名(connection’s bus names 或简单地称为 bus names(注:bus name指的是连接名,而不是总线名。))。连接名由”.”分开的字符组成,比如”com.acme.Foo”,中间的字符可以是字母、数字、连接线、下划线。每个连接拥有一个连接名。

当连接建立以后,D-Bus服务会分配一个不可改变的连接名,称为唯一连接名,这个连接名即使在进程结束后也不会再被其他进程所使用。唯一连接名以冒号开头,像是这个样子”:34-907″(后面的数字没有任何意义,仅用来区分不同连接)

此外,连接还可以引证另外的连接名,比如提供服务的进程可以起一个大家都知道的连接名,以便使用其服务的客户进程连接。这个名字必须包含两个以上的”.”,比如:”com.acme.PortableHole”。与唯一名不同的是,这个连接名可以转给其他进程使用。

Object Model(对象模型)

类似TCP及UDP协议中,信息的交换是对等的;数据总是从一个端口传输到另一个端口。D-Bus使用了复杂一些的模型,信息的发送方与信息的接收方永远不同。

以下我们将借用面向对象中的名词。其中很多如“对象(object)”及“方法(method)”等在D-Bus中具有特殊的意义,不要与具体语言中的相同名词混淆。

Objects(对象)

总线中信息流向的端点在D-Bus中称为对象。对象由客户进程创建,并在连接进程中保持不变。对象是客户进程提供服务的方式,当然客户进程可以创建多个对象。

总线的通信以对象为中心,其上的任何信息不外乎以下三类:

发送到对象上的请求;

从对象到请求进程的请求的响应;

单向的广播消息,发送到之前注册过这类消息的进程。

因此,从更高的角度看,总线支持两种形式的通信,“一对一的请求-响应”发送到对象,及来自于对象的“一对多的广播-订阅”

每条总线至少都有一个对象,这个对象代表总线本身。客户进程可以通过向这个总线对象发送请求来得到当前总线的状态。后面你将看到,总线对象有更多有用的功能。

Proxies(代理)

总线上的对象一般通过代理来访问。总线上的对象位于客户进程以外,而客户可以调用的本地接口与对象通信,此时,本地接口充当了代理的角色。是否要区分代理与对象的概念取决于你如何使用D-Bus。在Java语言中并不区分这两个概念,使你好像是在直接操作本地模块。但Glib的包装中明显地区分了代理与对象,并且提供了两类代理类型。代理只存在于客户进程的本地代码中,如何使用代理由所使用的编程语言决定。

对象拥有名称,它的构成类似于Unix文件系统中的文件路径,因此通常也称为路径(path)。如代表电子表格中某一栏内容的对象名称可能为”/org/kde/kspread/sheets/3/cells/4/5″。对象名在客户连接中要保持唯一性。为了取得电子表格中这栏内容的代理访问,你需要在电子表格的连接中请求”/org/kde/kspread/sheets/3/cells/4/5″对象。

既然任何对象都存在于连接之上,它的名称由连接名与对象本身名称组成。一旦你找到了你需要的对象,并想在程序过程中一直使用这个对象,你一般需要保存这个对象的代理变量。通过代理变量,你可以在以后操作这个对象。

一些语言的代理支持“断线重连”。比如你所连接的对象在某段时间里暂时断开了与总线的连接,代理会自动重连到相同的连接名并重新找到对象,你的程序甚至不会知道目标对象有段时间不可用。并不是所有的语言都支持这一特性,在GLib中的两种代理中的一种支持。但这一特性也并不总是理想的,比如你的一系列请求具有前后关联性,此时目标对象的断开与重新连接显然打破了这种连续性。在这种性况下,你应该使用连接的唯一名称进行连接(因为,唯一名称在断线重连后一定与之前的不同)。

Methods(方法)

客户向某对象发送一个请求的过程,看起来就像在这个对象上执行了一个方法:对象被请求执行一个明确的,有名称的动作。如果客户请求执行一个目标对象未提供的方法,将会产生一个错误。

方法的定义中可以支持输入参数。对于每个请求,都有一个包含请求结果以及结果数据(输出参数)的响应返回给请求者。当请求无法完成时,响应中将包含异常信息,其中至少有异常名称以及错误信息。

大多数语言都将这些封装在自身的语言机制中,比如将参数包装进消息包,将异常信息转换成语言自身的异常等等。在这些实现中,向远程对象传递一个字符串参数就好像是在本地执行一个字符串参数的函数一样简单。此时不再需要数据类型转换、数据复制等繁琐工作,语言本身封装了一切低层实现。

当然,请求与传统函数调用也有一个有趣的不同:当发送一个请求后,你不必一直等待响应。在复杂的程序中,你可以在响应到达之前去做其他的事。比如你可以去响应用户动作,或者处理网络/文件中的数据。你甚至可以同时连续发送请求后再处理响应,而不是以发送-响应-再发送的顺序。这种在等待响应过程中可以去做其他事情的调用,称为异步调用。如果你使用简单的发送-等待形式(同步调用),其他消息将会进入队列,当你空闲的时候处理。

Signals(信号)

信号,这种通信形式依然遵从面向对象概念,它是从对象发出但没有特定目的地址的单向数据广播。客户进程可以预先注册其感兴趣的信号,如特定名称的信号或从某个对象发出的信号等。当对象发出信号后,所有订阅了该信号的客户将收到此信号的复本。接收端可能有多种情况出现,或者有一个客户,或者有多个客户,或者根本没有客户对这个信号感兴趣。对于信号来说没有响应消息,发出信号的对象不会知道是不是有客户在接收,有多少客户接收,以及从客户端收到任何反馈。

与方法类似的是,信号可以有参数。由于信号是单向通信,因此其不可能像方法一样具有输入输出参数。D-Bus的最新版本允许客户通过参数比对过滤其需要的信号。

信号一般用来广播一些客户可能会感兴趣的事件,比如某个其他的客户与总线的连接断开等。这些信号来自总线对象,因此从信号中客户可以分辨断线是由于正常退出、被杀掉或者程序崩溃。

Interfaces(接口)

我们已经知道,每个对象都具有一些方法、以及可以产生特定的信号。方法与信号的集合称为这个对象的成员。

对象的所有成员都通过接口来定义。与JAVA类似,接口就是成员声明的集合。接口的实现就等于说这个接口提供了其所有方法及信号。所有成员必须按照接口声明的那样来提供服务。

对象可以都实现同一个接口,这与JAVA中不同的类可以实现同一个接口相似。另一方面,某个对象可以同时实现多个接口。与JAVA有所不同的是,D-Bus中没有实现任何接口的对象是没有意义的。对象所有支持的接口集合被称为对象的类型(object’s type)

当客户进程要执行一个对象方法或等待信号时,它必须指明要使用的对象及其对象成员。除此以外,客户可能还要指明对象成员所在的接口,有时这是必须的步骤。比如,当对象同时在其两个接口都实现foo方法时,若客户不指明要执行的是哪个接口上的foo方法,则会产生歧义。D-Bus的实现也可能会直接拒绝这类歧义请求。同样地,如果在订阅信号时不指明接口,则也有可能会收到不同接口发来的相同名字的信号,这通常不是预期的情况。D-Bus的较老版本中存在一个当出现歧义时丢失消息的BUG。

至于同一个接口内的对象是否可以重载,即接口内是否允许相同名字的对象取决于编程语言的实现。

总结

让我们来总结一下客户程序从还未连接到总线,直至找到其所需要的方法或订阅信号的整个流程。其中除了接口我们是可以忽略以外,其他步骤都是必须的。

名称 表示方法 看起来就是这样 由谁管理
总线 地址 unix:path=/var/run/dbus/system_bus_socket 系统设置
连接 连接地址

:34-907 (唯一名)

com.mycompany.TextEditor (公共名)

D-Bus(唯一名)

服务进程 (公共名)

对象 路径 /com/mycompany/TextFileManager 服务进程
接口 接口名 org.freedesktop.Hal.Manager 服务进程
成员 成员名 ListNames 服务进程

由上表可以看出,大多数名称都使用路径格式来表示。这很容易造成一定的困扰,尤其是这些名称取得很相像时,比如连接名为org.freedesktop.Hal,提供的对象为/org/freedesktop/Hal/Manager,实现的接口为org.freedesktop.Hal.Manager。另外,一些名称用斜线分隔另一些使用点号,也需要适应一段时间。

信息顺序

对于同一个对象的请求的到达顺序总会与其发送顺序一致。同样地,对同一目标的响应也会与对象发送顺序一致。

但这并不意味所有的消息都会按照发送时间的顺序到达。比如两个客户进程同时向同一个对象发送请求时,对象的接收顺序是不确定的。再比如客户向同一个对象连续发送两个请求,相应的两个响应顺序也是不确定的。对象本身的实现可能使用多线程或内部消息优先级等机制打乱消息的处理顺序

TODO: 线程

启动

到目前为止,我们所举的例子都是基于对象已经被服务进程创建的情况。除此之外,还一另外一种提供服务的方式:D-Bus服务进程可以按需要启动对应服务。有两种启动方式,它们都需要请求服务的客户提供要连接的公共名:

1. 使用总线对象所提供的方法

2. 使用连接公共名,直接调用目标连接上的对象方法

后一种方法可以在消息中设置参数来禁止。当你在创建代理而代理所对应的目标服务不存在时,一些编程语言会试图启动对应的服务,另一些语言会在你调用对象方法时再启动对应服务。这对于等待对象信号的情况有很大不同,如果目标对象根本不存在,你可能会永远等不到信号。

要建立可以自动启动的服务,需要设置服务配置文件。服务配置以UTF-8文本形式存放在.service文件中。

比如,如果你的程序提供/usr/local/bin/bankcounter对象并使用com.bigmoneybank.Deposits,com.bigmoneybank.Withdrawals两个连接名。则你可以像下面这样写一个”bankcounter.service”文件(文件名是任意的,但必须以.service结尾)

# (井号开头是注释)

# 下面一行是固定格式不要改动

[D-BUS Service]

Names=com.bigmoneybank.Deposits;com.bigmoneybank.Withdrawals

Exec=/usr/local/bin/bankcounter

Names一行指定了服务的连接名,以分号分隔。Exec一行指定启动这个服务的可执行文件路径。

这些服务配置文件所放置的位置在D-Bus设置文件的块中定义,默认为/usr/share/dbus-1/services/。如果在D-Bus运行过程中加入新的服务配置,则自动生效,无需重启。

关于这部分的进一步讨论参见Rapha?l Slinckx的DBus启动教程(DBus Activation Tutorial)

未来计划

这篇文档还未完工。一些主题还没有谈到,包括:

对象的实现

特殊的连接名:org.freedesktop.DBus(连接名就是方法名,无需对象!)

身份认证(参考这里:http://www.redhat.com/magazine/003jan05/features/dbus/)

Introspection

来源:http://www.freeworkzz.com/html/2010/07/dbus%E4%BB%8B%E7%BB%8D.html