什么是D-Bus?
D-Bus是一套进程间通信(IPC)系统。从结构上讲,它包括几个层次:
- 一个库,libdbus,它允许两个应用程序相互连接并交换信息。
- 一个消息总线守护进程的可执行文件,基于libdbus,多个应用程序可以连接其上。守护进程可以将一个应用程序的消息路由到零个或多个其他应用程序。
- 封装库或基于某个特定应用程序框架的绑定,例如,libdbus-glib和libdbus-qt。还有一些语言如Python的绑定。大多数人都应该使用这些封装库的API,因为它们简化了D-Bus编程细节。libdbus被有意设计成为更高层次绑定的后端。大部分libdbus的API仅仅是为了用来实现绑定。
libdbus只支持一对一的连接,类似于一个原始的网络套接字。不过,你在连接上发送的不是字节流,而是消息。消息包括一个用来识别消息类型的消息头,和一个包含数据包的消息体。libdbus还抽象了具体的传输过程(套接字或其他方式),并且处理一些诸如认证之类的具体事务。
消息总线守护进程就像一个轮子的轮毂。轮子的每一条轮辐都通过libdbus一对一的连接到应用程序。应用程序通过轮辐发送消息给总线守护进程,总线守护进程再将消息转发给连接其上的相应的应用程序。可以把守护进程想象成一个路由器。
在典型的计算机中总线守护进程有多个实例。第一个实例是一个全局的单实例,一个类似sendmail或Apache的系统守护进程。这个实例对接收何种消息有严格的安全限制,并且被用作整个系统内的通信。其他的实例是每个用户登录会话创建一个。这些实例允许用户会话的应用程序间相互通信。
系统的和用户的守护进程都是相对独立的。一般会话内的IPC不会涉及整个系统的消息总线进程,反之亦然。
D-Bus应用
D-Bus专为两种具体情况设计:
- 同一桌面会话的应用间的通信,以允许整合桌面会话成为一个整体,并解决进程的生命周期(桌面组件什么时候开始和停止运行)问题。
- 桌面会话和操作系统间的通信,操作系统通常包括内核和任何系统守护进程或进程。
就内置桌面会话而言,GNOME和KDE桌面有着截然不同的IPC解决方案经验,如CORBA和DCOP。D-Bus的建立基于这些经验,并且精心设计,以满足这些桌面应用的特殊需求。D-Bus可能适合也可能不适合其他应用。
D-Bus可能偶尔也会有除了设计目的以外的其他用途。它区别于其他形式的IPC的特性包括:
- 用于异步的二进制协议设计(本质上类似于X Window System协议)。
- 始终打开的,稳定的,可靠的连接。
- 消息总线是一个守护进程,而不是一个“集群”或分布式构架。
- 指明了许多实施和部署问题,而不是模糊的/可配置/可拔插的。
- 语义上类似于现存的DCOP系统,使得KDE更容易接受它。
- 用来支持系统消息总线模式的安全功能。
基本概念
一些无论你用什么应用程序框架编写D-Bus应用都需要的基本概念。实际的GLib或Qt或Python应用程序代码会有所不同。
本地对象和对象路径
你的编程框架可能定义了“对象”是什么样的,通常是一个基类。例如:java.lang.Object, GObject,QObject,python’s base Object,或任何其他形式。 让我们称之为本地对象。
低层次D-Bus协议,以及相应的libdbus API,不关心本地对象。然而,它提供了一个称之为对象路径的概念。对象路径的想法是,更高层次的绑定可以命名本地对象的实例,并允许远程应用程序引用它们。
对象路径看起来像一个文件系统路径,例如一个对象可以命名为/org/kde/kspread/sheets/3/cells/4/5。人类可读的路径是较好的,但你也可以创建一个对象名为/com/mycompany/c5yo817y0c1y1c5b如果它对您的应用程序有意义的话。
对象路径的命名空间是比较巧妙,只需在开头加上你自己的域名(如:/org/kde)。这将保持同一进程中的不同代码模块互不干扰。
方法和信号
每个对象都有成员;成员有两种类型,方法和信号。方法是一类可以在对象中被调用的操作,具有可选的输入(亦称参数或“入参”)和输出(亦称返回值或“出参”)。信号是对象发给任何感兴趣的观察者的广播;信号可能包含一个数据包。
方法和信号都通过名字被引用,如“Frobate”或“OnClicked”。
接口
每个对象都支持一个或多个接口。可以把接口想象成被命名的一组方法和信号,就像它在GLib或Qt或Java中一样。接口定义了一个接口对象实例的类型。
D-Bus通过一个简单的命名空间字符串来识别接口,如org.freedesktop.Introspectable。大多数绑定会直接映射这些接口名字到相应的编程语言结构,如Java的接口或C++的纯虚类。
代理
一个代理对象是一个用来代表另一个进程中的远程对象的合适的本地对象。低层次的D-Bus API包括手动的创建一个方法调用消息,发送,然后手动的接收和处理方法的回复消息。更高层次的绑定提供代理作为替代。代理看起来像一个普通的本地对象;但是当你调用一个代理对象的方法,绑定会将它转换成一个D-Bus的方法调用消息,等待回复消息,解包回复消息,并从本地方法中返回。
不使用代理的程序伪代码:
Message message = new Message("/remote/object/path", "MethodName", arg1, arg2); Connection connection = getBusConnection(); connection.send(message); Message reply = connection.waitForReply(message); if (reply.isError()) { } else { Object returnValue = reply.getReturnValue(); }
使用代理后:
Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path"); Object returnValue = proxy.MethodName(arg1, arg2);
总线名字
当每个应用程序连接到总线守护进程时,守护立即赋予它一个名字,称做唯一的连接名字。唯一的名字以一个‘:’(冒号)字符开头。这些名字在总线守护进程的生命周期中不会被重用——也就是说,你知道的一个给定的名字永远指向同一个应用。一个唯一名字的例子:34-907。冒号后面的数字除了唯一性以外没有任何意义。
当一个名字被映射到特定的应用程序的连接,该应用程序就说自己拥有这个名字。
应用程序可能会要求拥有额外的周知名字。例如,你可以写一个规范来定义一个名字叫做com.mycompany.TextEditor。你的协议可以指定自己拥有这个名字,一个应用程序应该在路径/com/mycompany/TextFileManager下有一个支持接口org.freedesktop.FileHandler的对象。
应用程序就可以发送消息到这个总线名字,对象,和接口以执行方法调用。
你可以把唯一名字比作IP地址,而把周知名字比作域名。所以,就像mycompany.com映射到192.168.0.5一样,com.mycompany.TextEditor可能映射到:34-907。
除了路由消息,名字还有第二重要的用途。它们被用来跟踪的生命周期。当一个应用程序退出(或奔溃),它到消息总线的连接会被操作系统关闭。消息总线随后发出通知消息告诉其余应用程序,这个应用程序的名字失去了他的拥有者。通过跟踪这些通知,你应用程序就可以可靠地监控其他应用程序的生命周期。
总线名字也可用于协调单实例应用。例如如果你想确保只有一个com.mycompany.TextEditor应用运行,只需让文本编辑器退出,如果发现总线名字已经有一个拥有者。
地址
使用D-Bus的应用程序可以是服务器或客户端。服务器侦听传入的连接;客户端连接到服务器。一旦连接建立起来,它就是一个对等的消息流,客户端和服务器只在建立连接时区分。
如果你可能使用总线守护进程,你的应用程序将是总线守护进程的一个客户端。也就是说,总线守护进程监听连接,你的应用程序初始化一个到总线守护进程的连接。
D-Bus地址指定了服务器监听哪和客户端连接到哪。例如,地址unix:path=/tmp/abcdef指定服务器将在UNIX域套接字的路径/tmp/abcdef下侦听,并且客户端将连接到这个套接字。地址也可以指定TCP/IP套接字,或将来的D-Bus升级规范中定义的任何传输协议。