| 级别: 初级 Ross Burton (), 软件工程师, OneEighty Software, Ltd.
2004 年 8 月 19 日 D-BUS 是一个大有前途的消息总线和活动系统,正开始深入地渗透到 Linux 桌面之中。了解创建它的原因、它的用途以及发展前景。
D-BUS 本质上是
进程间通信(inter-process communication)(IPC)的一个实现。不过,有一些
特性使得 D-BUS 远远不是“只是另一个 IPC 实现”。有很多不同的 IPC 实现,因为每一个都定位于解决
特定的明确定义的问题。CORBA 是用于面向对象编程中复杂的 IPC 的一个强大的解决方案。DCOP 是一个
较轻量级的 IPC 框架,功能较少,但是可以很好地集成到 K 桌面环境中。SOAP 和 XML-RPC 设计用于 Web
服务,因而使用 HTTP 作为其传输协议。D-BUS 设计用于桌面应用程序和 OS 通信。
典型的桌面都会有多个应用程序在运行,而且,它们经常需要彼此进行通信。DCOP 是一个用于 KDE 的
解决方案,但是它依赖于 Qt,所以不能用于其他桌面环境之中。类似的,Bonobo 是一个用于 GNOME 的
解决方案,但是非常笨重,因为它是基于 CORBA 的。它还依赖于 GObject,所以也不能用于 GNOME 之外。
D-BUS 的目标是将 DCOP 和 Bonobo 替换为简单的 IPC,并集成这两种桌面环境。由于尽可能地减少了
D-BUS 所需的依赖,所以其他可能会使用 D-BUS 的应用程序不用担心引入过多依赖。
术语“操作系统”在这里不仅包括内核,还包括系统后台进程。例如,通过使用 D-BUS 的
udev (Linux 2.6 中取代
devfs 的,
提供动态 /dev 目录),当设备(比如一个 USB 照相机)插入时会发放出一个信号。
这样可以更紧密地将硬件集成到桌面中,从而改善用户体验。
D-BUS 有一些有趣的特性,使其像是一个非常有前途的选择。
协议是低延迟而且低开销的,设计得小而高效,以便最小化传送的往返时间。另外,协议是二进制的,而
不是文本的,这样就排除了费时的序列化过程。由于只面向本地机器处理的使用情形,所以所有的消息
都以其自然字节次序发送。字节次序在每个消息中声明,所以如果一个 D-BUS 消息通过网络传输到
远程的主机,它仍可以被正确地识别出来。
从开发者的角度来看,D-BUS 是易于使用的。有线协议容易理解,客户机程序库以直观的方式对其进行
包装。
程序库还设计用于为其他系统所包装。预期,GNOME 将使用 GObject 创建包装 D-BUS 的包装器
(实际上这些已经部分存在了,将 D-BUS 集成入它们的事件循环),KDE 将使用 Qt 创建类似的
包装器。由于 Python 具有面向对象特性和灵活的类型,已经有了具备类似接口的 Python 包装器。
最后,D-BUS 正在 freedesktop.org 的保护下进行开发,在那里,来自 GNOME、KDE 以及
其他组织的对此感兴趣的成员参与了设计与实现。
典型的 D-BUS 设置将由几个总线构成。将有一个持久的
系统总线(system bus),它在
引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序
不能欺骗系统事件。还将有很多
会话总线(session buses),这些总线当用户登录后启动,属于
那个用户私有。它是用户的应用程序用来通信的一个会话总线。当然,如果一个应用程序需要接收
来自系统总线的消息,它不如直接连接到系统总线 —— 不过,它可以发送的消息将是受限的。
一旦应用程序连接到了一个总线,它们就必须通过添加
匹配器(matchers) 来声明它们希望
收到哪种消息。匹配器为可以基于接口、对象路径和方法进行接收的消息指定一组规则(见后)。
这样就使得应用程序可以集中精力去处理它们想处理的内容,以实现消息的高效路由,并保持总线
上消息的预期数量,以使得不会因为这些消息导致所有应用程序的性能下降并变得很慢。
本质上,D-BUS 是一个对等(peer-to-peer)的协议 —— 每个消息都有一个源和一个目的。这些地址
被指定为
对象路径。概念上,所有使用 D-BUS 的应用程序都包括一组
对象,消息发送到
或者发送自特定对象 —— 不是应用程序 —— 这些对象由对象路径来标识。
另外,每个对象都可以支持一个或多个
接口(interfaces)。这些接口看起来类似于 Java 中
的接口或者
C++ 中的纯粹的虚类(pure virtual classes)。不过,没
有选项来检查对象是否实现了它们所声明的接口,而且也没有办法可以调查对象内部以使列出其支持的接口。
接口用于名称空间和方法名称,因此一个单独的对象可以有名称相同而接口不同的多个方法。
在 D-BUS 中有四种类型的消息:方法调用(method calls)、方法返回(method returns)、信号(signals)
和错误(errors)。要执行 D-BUS 对象的方法,您需要向对象发送一个方法调用消息。它将完成一些处理并返回
一个方法返回消息或者错误消息。信号的不同之处在于它们不返回任何内容:既没有“信号返回”消息,也没有
任何类型的错误消息。
消息也可以有任意的参数。参数是强类型的,类型的范围是从基本的非派生类型(布尔(booleans)、
字节(bytes)、整型(integers))到高层次数据结构(字符串(strings)、数组( arrays)和字典(dictionaries))。
服务(Services) 是 D-BUS 的最高层次抽象,它们的实现当前还在不断发展变化。应用程序
可以通过一个总线来注册一个服务,如果成功,则应用程序就已经
获得 了那个服务。其他应用
程序可以检查在总线上是否已经存在一个特定的服务,如果没有可以要求总线启动它。服务抽象的细节 ——
尤其是服务活化 —— 当前正处于发展之中,应该会有变化。
尽管 D-BUS 相对较新,但是却迅速地得到了采用。如前所述,可以构建具有 D-BUS 支持的
udev 以使得当热插拔(hot-plug)设备时它可以发送一个信号。
任何应用程序都可以侦听这些事件并当接收到这些事件时执行动作。例如,gnome-volume-manager
可以检测到 USB 存储棒的插入并自动挂载它;或者,当插入一个数码相机时它可以自动下载照片。
一个更为有趣但很不实用的例子是 Jamboree 和 Ringaling 的结合。Jamboree 是一个简单的音乐播放器,
它具有 D-BUS 接口,以使得它可以被告知播放、到下一首歌、改变音量等等。Ringaling 是一个小程序,
它打开 /dev/ttyS0(一个串行端口)并观察接收到的内容。当 Ringaling 发现文本“RING”时,就通过
D-BUS 告知 Jamboree 减小音量。最终的结果是,如果您的计算机上插入了一个调制解调器,而且电话
铃响,则音乐音量就会为您减小。
这正是计算机所追求的!
现在,让我们来接触一些使用 D-BUS 代码的示例。
dbus-ping-send.c 每秒通过会话总线发送一个参数为字符串“Ping!”的信号。我使用 Glib 来
管理总线,以使得我不需要自己来处理总线的连接细节。
#include #include static gboolean send_ping (DBusConnection *bus); int main (int argc, char **argv) { GMainLoop *loop; DBusConnection *bus; DBusError error; /* Create a new event loop to run in */ loop = g_main_loop_new (NULL, FALSE); /* Get a connection to the session bus */ dbus_error_init (&error); bus = dbus_bus_get (DBUS_BUS_SESSION, &error); if (!bus) { g_warning ("Failed to connect to the D-BUS daemon: %s", error.message); dbus_error_free (&error); return 1; } /* Set up this connection to work in a GLib event loop */ dbus_connection_setup_with_g_main (bus, NULL); /* Every second call send_ping() with the bus as an argument*/ g_timeout_add (1000, (GSourceFunc)send_ping, bus); /* Start the event loop */ g_main_loop_run (loop); return 0; } static gboolean send_ping (DBusConnection *bus) { DBusMessage *message; /* Create a new signal "Ping" on the "com.burtonini.dbus.Signal" interface, * from the object "/com/burtonini/dbus/ping". */ message = dbus_message_new_signal ("/com/burtonini/dbus/ping", "com.burtonini.dbus.Signal", "Ping"); /* Append the string "Ping!" to the signal */ dbus_message_append_args (message, DBUS_TYPE_STRING, "Ping!", DBUS_TYPE_INVALID); /* Send the signal */ dbus_connection_send (bus, message, NULL); /* Free the signal now we have finished with it */ dbus_message_unref (message); /* Tell the user we send a signal */ g_print("Ping!\n"); /* Return TRUE to tell the event loop we want to be called again */ return TRUE; } |
main 函数创建一个 GLib 事件循环,获得会话总线的一个连接,
并将 D-BUS 事件处理集成到 Glib 事件循环之中。然后它创建了一个名为
send_ping
间隔为一秒的计时器,并启动事件循环。
send_ping 构造一个来自于对象路径 /com/burtonini/dbus/ping 和
接口
com.burtonini.dbus.Signal 的新的 Ping 信号。然后,字符串
“Ping!”作为参数添加到信号中并通过总线发送。在标准输出中会打印一条消息以让用户知道发送了
一个信号。
当然,不应该向总线发送了信号而没有任何程序在侦听它们……于是我们需要:
#include #include static DBusHandlerResult signal_filter (DBusConnection *connection, DBusMessage *message, void *user_data); int main (int argc, char **argv) { GMainLoop *loop; DBusConnection *bus; DBusError error; loop = g_main_loop_new (NULL, FALSE); dbus_error_init (&error); bus = dbus_bus_get (DBUS_BUS_SESSION, &error); if (!bus) { g_warning ("Failed to connect to the D-BUS daemon: %s", error.message); dbus_error_free (&error); return 1; } dbus_connection_setup_with_g_main (bus, NULL); /* listening to messages from all objects as no path is specified */ dbus_bus_add_match (bus, "type='signal',interface='com.burtonini.dbus.Signal'"); dbus_connection_add_filter (bus, signal_filter, loop, NULL); g_main_loop_run (loop); return 0; } static DBusHandlerResult signal_filter (DBusConnection *connection, DBusMessage *message, void *user_data) { /* User data is the event loop we are running in */ GMainLoop *loop = user_data; /* A signal from the bus saying we are about to be disconnected */ if (dbus_message_is_signal (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")) { /* Tell the main loop to quit */ g_main_loop_quit (loop); /* We have handled this message, don't pass it on */ return DBUS_HANDLER_RESULT_HANDLED; } /* A Ping signal on the com.burtonini.dbus.Signal interface */ else if (dbus_message_is_signal (message, "com.burtonini.dbus.Signal", "Ping")) { DBusError error; char *s; dbus_error_init (&error); if (dbus_message_get_args (message, &error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) { g_print("Ping received: %s\n", s); dbus_free (s); } else { g_print("Ping received, but error getting message: %s\n", error.message); dbus_error_free (&error); } return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } |
这个程序侦听 dbus-ping-send.c 正在发出的信号。
main 函数
和前面一样启动,创建一个到总线的连接。然后它声明愿意当具有
com.burtonini.dbus.Signal
接口的信号被发送时得到通知,将
signal_filter 设置为通知函数,
然后进入事件循环。
当满足匹配的消息被发送时,
signal_func 会被调用。不过,它也将会
收到来自总线本身的总线管理信号。要确定接收到消息时应该做些什么,仅仅需要检验消息头。如果消息是
总线断开信号,则事件循环终止,因为侦听一个不存在的总线是没有意义的。(告知总线信号已经处理)。
然后,将到来的消息与期望的消息相比较,如果成功,则解出参数并输出。如果到来的消息不是其中的任何一个,
则告知总线没有处理那个消息。
那两个示例使用了低层的 D-BUS 程序库,这个程序库是完全的,但是当您想创建服务和很多对象时,
使用起来冗长得令人厌倦。有正在开发中的 C# 和 Python 包装器,提供了非常接近于 D-BUS 的逻辑
模型的编程接口。作为一个示例,这里是用 Python 重新对 ping/listen 示例进行了更为精致的实现。
由于 Python 绑定模拟了逻辑接口,所以不可能不通过一个服务来发送信号。所以这个例子也要创建
一个服务:
#! /usr/bin/env python import gtk import dbus # Connect to the bus bus = dbus.Bus() # Create a service on the bus service = dbus.Service("com.burtonini.dbus.SignalService", bus) # Define a D-BUS object class SignalObject(dbus.Object): def __init__(self, service): dbus.Object.__init__(self, "/", [], service) # Create an instance of the object, which is part of the service signal_object = SignalObject(service) def send_ping(): signal_object.broadcast_signal("com.burtonini.dbus.Signal", "Ping") print "Ping!" return gtk.TRUE # Call send_ping every second to send the signal gtk.timeout_add(1000, send_ping) gtk.main()
|
代码大部分是不言而明的:获得一个到总线的连接并注册
com.burtonini.dbus.SignalService 服务。然后创建一个最小限度的 D-BUS 对象,
这个对象每秒广播一个信号。代码比相应的
C 代码 更简单,
但是需要做 Python 绑定的工作。(例如,没有方法向信号添加参数。)
#! /usr/bin/env python import gtk import dbus bus = dbus.Bus() def signal_callback(interface, signal_name, service, path, message): print "Received signal %s from %s" % (signal_name, interface) # Catch signals from a specific interface and object, and call signal_callback # when they arrive. bus.add_signal_receiver(signal_callback, "com.burtonini.dbus.Signal", # Interface None, # Any service "/" # Path of sending object ) # Enter the event loop, waiting for signals gtk.main()
|
此代码比 dbus-ping-listen.c 中相应的
C 代码 更简明,也更容易读懂。此外,
有些地方需要做绑定的工作(当调用
bus.add_signal_receiver 时,用户必须传入
一个接口和一个对象路径;否则会创建不正常的匹配器)。这是一个微不足道的缺陷,一旦这个缺陷被修正,就可以
去服务和对象路径参数除,这将进一步提高代码的可读性。
D-BUS 是一个轻量级但是很强大的远程过程调用系统,为希望使用它的应用程序带来最小的开销代价。
D-BUS 正由一组非常有经验的程序员进行积极的公开开发。D-BUS 得到了早期采用者的迅速接受,因而它在 Linux
桌面领域似乎有一个乐观的未来。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
-
在
可以找到资料、
下载、文档及更多内容。
-
D-BUS 作为
的一部分进行开发。
-
是一个强大的、标准化的远程过程调用规范。
-
是 GNOME 中使用的
一个 CORBA 实现。(GNOME 组件系统
构建于 ORBit 之上)。
- KDE 的远程过程调用实现是
DCOP。
-
的目标是通过 D-BUS 实现 Linux 中硬件的无缝集成。
-
想要在电话铃响时减少您的音乐播放器的音量吗?您需要
(以及一个可以识别 D-BUS 的音乐播放器,比如
)。
-
Ross 以前为 developerWorks 撰写了
在 Python 中封装 GObject(developerWorks,2003 年 3 月)一文,该文展示了您如何在 Python 中
随时使用 C-coded GObject,而不管您是否特别精通
C 。
-
教程
Bridging
XPCOM/Bonobo: Techniques(developerWorks,2001 年 5 月)
讨论了一些必要的概念和技术,它们用于将两个组件体系结构连接起来,以使一个体系结构中的组件可以在另一个环境中使用。
-
用 DCOP 连接 KDE 应用程序(developerWorks,2004 年 2 月)介绍了 KDE 的进程间通信协议及其脚本化。
-
使进程和线程同步
(developerWorks,2001 年 10 月)考察了利用进程间同步原语作为控制两个进程访问同一资源的一种方式。
-
CORBA
Component Model (CCM)概述了 CORBA 规范以及 CORBA 与其他组件模型的互操作性。
-
在
developerWorks Linux 专区 可以找到
更多为 Linux 开发者准备的参考资料。
-
在 Developer Bookstore Linux 区中定购
打折出售的 Linux 书籍。
- 从 developerWorks 的
Speed-start
your Linux app 专区下载可以运行于 Linux 之上的经过挑选的 developerWorks Subscription
产品免费测试版本,包括 WebSphere Studio Site Developer、WebSphere SDK for Web services、WebSphere
Application Server、DB2 Universal Database Personal Developers Edition、Tivoli
Access Manager 和 Lotus Domino Server。要更快速地开始上手,请参阅针对各个产品的 how-to 文章和技术支持。
|