DBUS基础知识

原文 http://www.cnblogs.com/wzh206/archive/2010/05/13/1734901.html
1. 进程间使用D-Bus通信
[indent] D-Bus是一种高级的进程间通信机制,它由freedesktop.org项目提供,使用GPL许可证发行。D-Bus最主要的用途是在Linux桌面环境为进程提供通信,同时能将Linux桌面环境和Linux内核事件作为消息传递到进程。D-Bus的主要概率为总线,注册后的进程可通过总线接收或传递消息,进程也可注册后等待内核事件响应,例如等待网络状态的转变或者计算机发出关机指令。目前,D-Bus已被大多数Linux发行版所采用,开发者可使用D-Bus实现各种复杂的进程间通信任务。
[/indent] 2. D-Bus的基本概念
[indent] D-Bus是一个消息总线系统,其功能已涵盖进程间通信的所有需求,并具备一些特殊的用途。D-Bus是三层架构的进程间通信系统,其中包括:
[/indent] [indent] 接口层:接口层由函数库libdbus提供,进程可通过该库使用D-Bus的能力。
[/indent] [indent] 总线层:总线层实际上是由D-Bus总线守护进程提供的。它在Linux系统启动时运行,负责进程间的消息路由和传递,其中包括Linux内核和Linux桌面环境的消息传递。
[/indent] [indent] 包装层:包装层一系列基于特定应用程序框架的Wrapper库。
[/indent] [indent] D-Bus具备自身的协议,协议基于二进制数据设计,与数据结构和编码方式无关。该协议无需对数据进行序列化,保证了信息传递的高效性。无论是libdbus,还是D-Bus总线守护进程,均不需要太大的系统开销。
[/indent] [indent] 总线是D-Bus的进程间通信机制,一个系统中通常存在多条总线,这些总线由D-Bus总线守护进程管理。最重要的总线为系统总线(System Bus),Linux内核引导时,该总线就已被装入内存。只有Linux内核、Linux桌面环境和权限较高的程序才能向该总线写入消息,以此保障系统安全性,防止有恶意进程假冒Linux发送消息。
[/indent] [indent] 会话总线(Session Buses)由普通进程创建,可同时存在多条。会话总线属于某个进程私有,它用于进程间传递消息。
[/indent] [indent] 进程必须注册后才能收到总线中的消息,并且可同时连接到多条总线中。D-Bus提供了匹配器(Matchers)使进程可以有选择性的接收消息,另外运行进程注册回调函数,在收到指定消息时进行处理。匹配器的功能等同与路由,用于避免处理无关消息造成进程的性能下降。除此以外,D-Bus机制的重要概念有以下几个。
[/indent] [indent] 对象:对象是封装后的匹配器与回调函数,它以对等(peer-to-peer)协议使每个消息都有一个源地址和一个目的地址。这些地址又称为对象路径,或者称之为总线名称。对象的接口是回调函数,它以类似C++的虚拟函数实现。当一个进程注册到某个总线时,都要创建相应的消息对象。
[/indent] [indent] 消息:D-Bus的消息分为信号(signals)、方法调用(method calls)、方法返回(method returns)和错误(errors)。信号是最基本的消息,注册的进程可简单地发送信号到总线上,其他进程通过总线读取消息。方法调用是通过总线传递参数,执行另一个进程接口函数的机制,用于某个进程控制另一个进程。方法返回是注册的进程在收到相关信息后,自动做出反应的机制,由回调函数实现。错误是信号的一种,是注册进程错误处理机制之一。
[/indent] [indent] 服务:服务(Services)是进程注册的抽象。进程注册某个地址后,即可获得对应总线的服务。D-Bus提供了服务查询接口,进程可通过该接口查询某个服务是否存在。或者在服务结束时自动收到来自系统的消息。
[/indent] [indent] 建立服务的流程:

建立一个dbus连接之后 – dbus_bus_get(),为这个dbus连接(DbusConnection)起名 – dbus_bus_request_name(),这个名字将会成为我们在后续进行远程调用的时候的服务名,然后我们进入监听循环 – dbus_connection_read_write()。在循环中,我们从总线上取出消息 – dbus_connection_pop_message(),并通过比对消息中的方法接口名和方法名 – dbus_message_is_method_call(),如果一致,那么我们跳转到相应的处理中去。在相应的处理中,我们会从消息中取出远程调用的参数。并且建立起回传结果的通路 – reply_to_method_call()。回传动作本身等同于一次不需要等待结果的远程调用。
发送信号的流程:

建立一个dbus连接之后,为这个dbus连接起名,建立一个发送信号的通道,注意,在建立通道的函数中,需要我们填写该信号的接口名和信号名 – dbus_message_new_signal()。然后我们把信号对应的相关参数压进去 – dbus_message_iter_init_append(); dbus_message_iter_append_basic()。然后就可以启动发送了 – dbus_connection_send(); dbus_connection_flush。
进行一次远程调用的流程:

建立好dbus连接之后,为这dbus连接命名,申请一个远程调用通道 – dbus_message_new_method_call(),注意,在申请远程调用通道的时候,需要填写服务器名,本次调用的接口名,和本次调用名(方法名)。压入本次调用的参数 – dbus_message_iter_init_append(); dbus_message_iter_append_basic(),实际上是申请了一个首地址,我们就是把我们真正要传的参数,往这个首地址里面送(送完之后一般都会判断是否内存越界了)。然后就是启动发送调用并释放发送相关的消息结构 – dbus_connection_send_with_reply()。这个启动函数中带有一个句柄。我们马上会阻塞等待这个句柄给我们带回总线上回传的消息。当这个句柄回传消息之后,我们从消息结构中分离出参数。用dbus提供的函数提取参数的类型和参数 – dbus_message_iter_init(); dbus_message_iter_next(); dbus_message_iter_get_arg_type(); dbus_message_iter_get_basic()。也就达成了我们进行本次远程调用的目的了。
信号接收流程:

建立一个dbus连接之后,为这个dbus连接起名,为我们将要进行的消息循环添加匹配条件(就是通过信号名和信号接口名来进行匹配控制的) – dbus_bus_add_match()。我们进入等待循环后,只需要对信号名,信号接口名进行判断就可以分别处理各种信号了。在各个处理分支上。我们可以分离出消息中的参数。对参数类型进行判断和其他的处理。
dbus_connection_read_write()

As long as the connection is open, this function will block until it can read or write, then read or write, then return #TRUE.
If the connection is closed, the function returns #FALSE.
dbus_connection_pop_message()

Returns the first-received message from the incoming message queue, removing it from the queue. The caller owns a reference to the returned message. If the queue is empty, returns #NULL.
dbus_connection_send()

Adds a message to the outgoing message queue. Does not block to write the message to the network; that happens asynchronously. To force the message to be written, call dbus_connection_flush(). Because this only queues the message, the only reason it can
fail is lack of memory. Even if the connection is disconnected, no error will be returned.
@param connection the connection.
@param message the message to write.
@param serial return location for message serial, or #NULL if you don’t care
@returns #TRUE on success.
dbus_connection_send_with_reply()

Queues a message to send, as with dbus_connection_send(), but also returns a #DBusPendingCall used to receive a reply to the message. If no reply is received in the given timeout_milliseconds, this function expires the pending reply and generates a synthetic error reply (generated in-process, not by the remote application) indicating that a timeout occurred.
A #DBusPendingCall will see a reply message before any filters or registered object path handlers. See dbus_connection_dispatch() for details on when handlers are run.
A #DBusPendingCall will always see exactly one reply message, unless it’s cancelled with dbus_pending_call_cancel().
If #NULL is passed for the pending_return, the #DBusPendingCall will still be generated internally, and used to track the message reply timeout. This means a timeout error will occur if no reply arrives, unlike with dbus_connection_send().
If -1 is passed for the timeout, a sane default timeout is used. -1 is typically the best value for the timeout for this reason, unless you want a very short or very long timeout. There is no way to avoid a timeout entirely, other than passing INT_MAX for the
timeout to mean “very long timeout.” libdbus clamps an INT_MAX timeout down to a few hours timeout though.
@warning if the connection is disconnected, the #DBusPendingCall will be set to #NULL, so be careful with this.
@param connection the connection
@param message the message to send
@param pending_return return location for a #DBusPendingCall object, or #NULL if connection is disconnected
@param timeout_milliseconds timeout in milliseconds or -1 for default
@returns #FALSE if no memory, #TRUE otherwise.
dbus_message_is_signal()

Checks whether the message is a signal with the given interface and member fields. If the message is not #DBUS_MESSAGE_TYPE_SIGNAL, or has a different interface or member field, returns #FALSE.
dbus_message_iter_init()

Initializes a #DBusMessageIter for reading the arguments of the message passed in.
dbus_message_iter_next()

Moves the iterator to the next field, if any. If there’s no next field, returns #FALSE. If the iterator moves forward, returns #TRUE.
dbus_message_iter_get_arg_type()

Returns the argument type of the argument that the message iterator points to. If the iterator is at the end of the message, returns #DBUS_TYPE_INVALID.
dbus_message_iter_get_basic()

Reads a basic-typed value from the message iterator. Basic types are the non-containers such as integer and string.
dbus_message_new_signal()

Constructs a new message representing a signal emission. Returns #NULL if memory can’t be allocated for the message. A signal is identified by its originating object path, interface, and the name of the signal.
Path, interface, and signal name must all be valid (the D-Bus specification defines the syntax of these fields).
@param path the path to the object emitting the signal
@param interface the interface the signal is emitted from
@param name name of the signal
@returns a new DBusMessage, free with dbus_message_unref()
dbus_message_iter_init_append()

Initializes a #DBusMessageIter for appending arguments to the end of a message.
@param message the message
@param iter pointer to an iterator to initialize
dbus_message_iter_append_basic()

Appends a basic-typed value to the message. The basic types are the non-container types such as integer and string.
@param iter the append iterator
@param type the type of the value
@param value the address of the value
@returns #FALSE if not enough memory
dbus_message_new_method_call()

Constructs a new message to invoke a method on a remote object. Returns #NULL if memory can’t be allocated for the message. The destination may be #NULL in which case no destination is set; this is appropriate when using D-Bus in a peer-to-peer context (no message bus). The interface may be #NULL, which means that if multiple methods with the given name exist it is undefined which one will be invoked.
The path and method names may not be #NULL.
Destination, path, interface, and method name can’t contain any invalid characters (see the D-Bus specification).
@param destination name that the message should be sent to or #NULL
@param path object path the message should be sent to
@param interface interface to invoke method on, or #NULL
@param method method to invoke
@returns a new DBusMessage, free with dbus_message_unref()
dbus_bus_get()

Connects to a bus daemon and registers the client with it. If a connection to the bus already exists, then that connection is returned. The caller of this function owns a reference to the bus.
@param type bus type
@param error address where an error can be returned.
@returns a #DBusConnection with new ref

[/indent] [indent] 安装D-Bus可在其官方网站下载源码编译,地址为http://dbus.freedesktop.org。或者在终端上输入下列指令:
[/indent] [list=1]
[list=1]
[]yum install dbus dbus-devel dbus-doc
[/list][/list] [indent] 安装后,头文件位于"/usr/include/dbus-<版本号>/dbus"目录中,编译使用D-Bus的程序时需加入编译指令"pkg-config --cflags --libs dbus-1"。
[/indent] 3. D-Bus的用例
[indent] 在使用GNOME桌面环境的Linux系统中,通常用GLib库提供的函数来管理总线。在测试下列用例前,首先需要安装GTK+开发包(见22.3节)并配置编译环境。该用例一共包含两个程序文件,每个程序文件需单独编译成为可执行文件。
[/indent] [indent] 1.消息发送程序
[/indent] [indent] "dbus-ding-send.c"程序每秒通过会话总线发送一个参数为字符串Ding!的信号。该程序的源代码如下:
[/indent] [indent] [list=1]
[
]#include <glib.h> // 包含glib库
[]#include <dbus/dbus-glib.h> // 包含
glib库中D-Bus管理库
[
]#include <stdio.h>
[]static gboolean send_ding(DBusConnection bus);// 定义发送消息函数的原型
[
]int main ()
[
]{
[] GMainLoop loop; // 定义一个事件循环对象的指针
[
] DBusConnection bus; // 定义总线连接对象的指针
[
] DBusError error; // 定义D-Bus错误消息对象
[
] loop = g_main_loop_new(NULL, FALSE); // 创建新事件循环对象
[] dbus_error_init (&error); // 将错误消息对象连接到D-Bus
[
] // 错误消息对象
[] bus = dbus_bus_get(DBUS_BUS_SESSION, &error);// 连接到总线
[
] if (!bus) { // 判断是否连接错误
[]g_warning(“连接到D-Bus失败: %s”, error.message);
[
] // 使用GLib输出错误警告信息
[] dbus_error_free(&error); // 清除错误消息
[
] return 1;
[] }
[
] dbus_connection_setup_with_g_main(bus, NULL);
[] // 将总线设为接收GLib事件循环
[
] g_timeout_add(1000, (GSourceFunc)send_ding, bus);
[] // 每隔1000ms调用一次send_ding()函数
[
] // 将总线指针作为参数
[] g_main_loop_run(loop); // 启动事件循环
[
] return 0;
[]}
[
]static gboolean send_ding(DBusConnection bus) // 定义发
送消息函数的细节
[
]{
[] DBusMessage message; // 创建消息对象指针
[
] message = dbus_message_new_signal(“/com/burtonini/dbus/ding”,
[
] “com.burtonini.dbus.Signal”,
[] “ding”); // 创建消息对象并标识路径
[
] dbus_message_append_args(message,
[] DBUS_TYPE_STRING, “ding!”,
[
] DBUS_TYPE_INVALID); //将字符串Ding!定义为消息
[] dbus_connection_send(bus, message, NULL); // 发送该消息
[
] dbus_message_unref(message); // 释放消息对象
[] g_print(“ding!\n”); // 该函数等同与标准输入输出
[
] return TRUE;
[]}
[/list] main()函数创建一个GLib事件循环,获得会话总线的一个连接,并将D-Bus事件处理集成到GLib事件循环之中。然后它创建了一个名为send_ding()函数作为间隔为一秒的计时器,并启动事件循环。send_ding()函数构造一个来自于对象路径"/com/burtonini/dbus/ding"和接口"com.burtonini.dbus.Signal"的新的Ding信号。然后,字符串Ding!作为参数添加到信号中并通过总线发送。在标准输出中会打印一条消息以让用户知道发送了一个信号。
[/indent] [indent] 2.消息接收程序
[/indent] [indent] dbus-ding-listen.c程序通过会话总线接收dbus-ding-send.c程序发送到消息。该程序的源代码如下:
[/indent] [indent] [list=1]
[
]#include <glib.h> // 包含glib库
[]#include <dbus/dbus-glib.h> // 包含glib库中D-Bus管理库
[
]static DBusHandlerResult signal_filter // 定义接收消息函数的原型
[] (DBusConnection connection, DBusMessage message, void user_data);
[
]int main()
[
]{
[
] GMainLoop loop; // 定义一个事件循环对象的指针
[
] DBusConnection bus; // 定义总线连接对象的指针
[
] DBusError error; // 定义D-Bus错误消息对象
[
] loop = g_main_loop_new(NULL, FALSE); // 创建新事件循环对象
[] dbus_error_init(&error); // 将错误消息对象连接到D-Bus
[
] // 错误消息对象
[] bus = dbus_bus_get(DBUS_BUS_SESSION, &error); // 连接到总线
[
] if (!bus) { // 判断是否连接错误
[]g_warning(“连接到D-Bus失败: %s”, error.message);
[
] // 使用GLib输出错误警告信息
[] dbus_error_free(&error); // 清除错误消息
[
] return 1;
[] }
[
] dbus_connection_setup_with_g_main(bus, NULL);
[] // 将总线设为接收GLib事件循环
[
] 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)
[
]{
[
] GMainLoop loop = user_data; // 定义事件循环对象的指针,并与主函数中的同步
[
] if (dbus_message_is_signal // 接收连接成功消息,判断是否连接失败
[
] (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL,
“Disconnected”)) {
[
] g_main_loop_quit (loop); // 退出主循环
[] return DBUS_HANDLER_RESULT_HANDLED;
[
] }
[] if (dbus_message_is_signal(message, “com.burtonini.dbus.Signal”,
[
] “Ping”)) {
[] // 指定消息对象路径,判断是否成功
[
] DBusError error; // 定义错误对象
[] char s;
[
]dbus_error_init(&error); // 将错误消息对象连接到D-Bus错误
[
] // 消息对象
[] if (dbus_message_get_args // 接收消息,并判断是否有错误
[
] (message, &error, DBUS_TYPE_STRING, &s,
DBUS_TYPE_INVALID)) {
[] g_print(“接收到的消息是: %s\n”, s); // 输出接收到的消息
[
] dbus_free (s); // 清除该消息
[] }
[
] else { // 有错误时执行下列语句
[] g_print(“消息已收到,但有错误提示: %s\n”, error.message);
[
] dbus_error_free (&error);
[] }
[
] return DBUS_HANDLER_RESULT_HANDLED;
[] }
[
] return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
[*]}
[/list] 该程序侦听dbus-ping-send.c程序正在发出的信号。main()函数和前面一样启动,创建一个到总线的连接。然后它声明愿意在使用com.burtonini.dbus.Signal接口的信号被发送时得到通知,将signal_filter()函数设置为通知函数,然后进入事件循环。当满足匹配的消息被发送时,signal_func()函数会被调用。
如果需要确定在接收消息时如何处理,可通过检测消息头实现。若收到的消息为总线断开信号,则主事件循环将被终止,因为监听的总线已经不存在了。若收到其他的消息,首先将收到的消息与期待的消息进行比较,两者相同则输出其中参数,并退出程序。两者不相同则告知总线并没有处理该消息,这样消息会继续保留在总线中供别的程序处理。
[/indent]

好牛逼的内容,值得学习