0%

D-Bus详解与应用

D-Bus的方式在移动手机操作系统中非常重要,包括Maemo,Moblin等以Linux为基础的操作系统。
参考链接:D-bus简介 , 使用 D-BUS 连接桌面应用程序 , D-总线规格


D-bus简介

什么是D-Bus

有很多IPC(interprocess communication ) ,用于不同的解决方案:CORBA 是用于面向对象编程中复杂的 IPC 的一个强大的解决方案。DCOP 是一个较轻量级的 IPC 框架,功能较少,但是可以很好地集成到 K 桌面环境中。SOAP 和 XML-RPC 设计用于 Web 服务,因而使用 HTTP 作为其传输协议。D-BUS 设计用于桌面应用程序和 OS 通信。D-Bus(其中D原先是代表桌面“Desktop” 的意思),即:用于桌面操作系统的通信总线。现在逐渐被引入到嵌入式系统中,不过名字还是保留原先的叫法而已。

D-Bus的特性

  • 低延迟、低开小,设计小而高效
  • 二进制协议而不是文本,去除了序列化过程
  • 结构化的名字空间
  • 独立于架构的数据格式
  • 支持消息中的大部分通用数据元素
  • 带有异常处理的通用远程调用接口
  • 支持广播类型的通信

D-Bus的内容

D-Bus是一个为应用程序间通信消息总线系统, 用于进程之间的通信。它是个3层架构的IPC 系统,包括:

  • 函数库libdbus ,用于两个应用程序互相联系和交互消息。
  • 一个基于libdbus构造的消息总线守护进程,可同时与多个应用程序相连,并能把来自一个应用程序的消息路由到0或者多个其他程序。
  • 基于特定应用程序框架的封装库或捆绑(wrapper libraries or bindings )。例如,libdbus-glib和libdbus-qt,还有绑定在其他语言,例如Python的。大多数开发者都是使用这些封装库的API,因为它们简化了D-Bus编程细节。libdbus被有意设计成为更高层次绑定的底层后端(low-level backend )。大部分libdbus的 API仅仅是为了用来实现绑定。

D-Bus的内部工作方式

典型的 D-BUS 设置将由几个总线构成。将有一个持久的 系统总线(system bus),它在 引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序 不能欺骗系统事件。还将有很多 会话总线(session buses),这些总线当用户登录后启动,属于 那个用户私有。它是用户的应用程序用来通信的一个会话总线。当然,如果一个应用程序需要接收 来自系统总线的消息,它不如直接连接到系统总线 —— 不过,它可以发送的消息将是受限的。

一旦应用程序连接到了一个总线,它们就必须通过添加 匹配器(matchers) 来声明它们希望 收到哪种消息。匹配器为可以基于接口、对象路径和方法进行接收的消息指定一组规则(见后)。 这样就使得应用程序可以集中精力去处理它们想处理的内容,以实现消息的高效路由,并保持总线 上消息的预期数量,以使得不会因为这些消息导致所有应用程序的性能下降并变得很慢。

消息类型

在 D-BUS 中有四种类型的消息:方法调用(method calls)、方法返回(method returns)、信号(signals) 和错误(errors)。要执行D-BUS对象的方法,您需要向对象发送一个方法调用消息。它将完成一些处理并返回 一个方法返回消息或者错误消息。信号的不同之处在于它们不返回任何内容:既没有“信号返回”消息,也没有 任何类型的错误消息。

消息也可以有任意的参数。参数是强类型的,类型的范围是从基本的非派生类型(布尔(booleans)、 字节(bytes)、整型(integers))到高层次数据结构(字符串(strings)、数组( arrays)和字典(dictionaries))。

DBusBusType

Name Type
DBUS_BUS_SESSION 登录的会话总线
DBUS_BUS_SYSTEM 系统范围的总线
DBUS_BUS_STARTER 启动的线程(如果有的话)

DBusHandlerResult

Name Result
DBUS_HANDLER_RESULT_HANDLED 消息已产生效果-无需运行更多处理程序。
DBUS_HANDLER_RESULT_NOT_YET_HANDLED 消息没有任何效果-查看其他处理程序是否需要它。
DBUS_HANDLER_RESULT_NEED_MEMORY 需要更多内存才能返回DBUS_HANDLER_RESULT_HANDLEDDBUS_HANDLER_RESULT_NOT_YET_HANDLED。请稍后再尝试使用更多内存。

DBusError

Usage Meanings
const char* name 公共的error名称字段
const char* message 公共的error消息字段

D-bus应用

D-Bus文档:D-Bus 1.13.16

常用D-Bus函数

1
2
dbus_g_bus_get (DBusBusType type,
DBusError* error);

Function:

r给指定的总线返回一个连接,这个连接是与该函数的其他调用者共享的全局变量。

Parameters:

Type:DBusBusType

error:错误可以被返回的地址

1
dbus_g_thread_init(void);

初始化D-Bus线程系统。此函数只能被调用一次,并且必须调用在D-Bus的API中任何其他函数之前。

1
dbus_error_init()

Function:

初始化DBusError结构。

Parameters:

error:DBusError

1
dbus_error_free (&error);

Function:

释放已设置或者刚刚初始化的错误,然后像dbus_error_init()一样重新初始化该错误

Parameters:

&error:存储错误的内存

1
2
dbus_connection_setup_with_g_main (DBusConnection *connection,
GMainContext *context);

Function:

设置DBusConnection的监视和超时功能,将连接与GLib主循环集成在一起。

Parameters:

connection:GMainContext内容,如果为NULL,将会使用默认的context;

context:设置为True表示循环正在运行;True或者FLASE都无所谓,在调用g_main_loop_run()之后,该设置都会被设置为True

1
2
3
4
5
dbus_message_new_signal	(	
const char * path,
const char * iface,
const char * name

Function:

构造一个表示消息发射的新消息。

如果无法为消息分配内存,则返回NULL。

Parameters:

Path:发出signal的对象路径

iface:发出signal的接口

name:signal的名称

1
2
3
4
dbus_g_proxy_new_for_name (DBusGConnection *connection,
const char *name,
const char *path,
const char *iface);

为通过消息总线上的连接导出的远程接口创建新的代理。通过该代理的方法调用和信号连接将转到名称所有者;该名称的所有者应支持给定的接口名称。所有者的名称可能会随着时间变化,例如在两个不同的方法调用之间,除非名称是唯一的名称。如果需要固定所有者,则需要请求当前所有者,并将代理绑定到其唯一名称而不是通用名称,参考 dbus_g_proxy_new_for_name_owner ()

与名称相关的代理仅对消息总线有意义,而不适用于应用程序对应用程序的直接dbus连接。

仅当DBusConnection断开连接,该代理没有剩余引用或该名称是唯一名称并且其所有者消失时,此代理才会发出“销毁”信号。 如果一个“众所周知”的名称更改了所有者,则代理将仍然有效。

1
2
3
4
dbus_g_proxy_new_for_name_owner( ,DBusGConnection *connection,


);const char *nameconst char *pathconst char *ifaceGError **error

dbus_g_proxy_new_for_name()类似,但却是想消息总线发出一个往返请求来获取当前名称所有者,然后将代理绑定到当前所有者的唯一名称,而不是well-known name。结果,名称所有者将不随时间改变,并且当所有者从消息总线中消失时,代理将发出“销毁”的信号。

两者区别举例:

如果你使用了org.freedesktop.Database作为一个“well-known name”,哪怕所有者更改,dbus_g_proxy_new_for_name 依然会绑定到该名称;而dbus_g_proxy_new_for_name_owner则会绑定到该所有者的唯一名称,而不是通用名称。

1
2
3
4
5
6
dbus_connection_send_with_reply	(	
DBusConnection * connection,
DBusMessage * message,
DBusPendingCall ** pending_return,
int timeout_milliseconds
)

Function:

对要发送的消息进行排队,与dbus_connection_send()相同,但也返回一个用于接收对消息的答复的DBusPendingCall
如果在给定的超时时间内没有收到任何回复,则此函数会使挂起的回复过期,并生成一个合成错误回复(进程中生成,而不是由远程应用程序生成),指示发生了超时。

Parameters:

connection:the connection

message:the message to send

pending_return:返回DBusPendingCall对象的位置;如果连接断或者尝试在不支持Unix文件描述符的连接数发送它们,则返回NULL

timeout_milliseconds:超时以毫秒为单位,-1(或DBUS_timeout_USE_DEFAULT),或DBUS_timeout_INFINITE表示无超时

D-Bus流程

建立服务的流程:

  1. 建立一个dbus连接:dbus_bus_get()

  2. 为这个dbus连接(DbusConnection)起名:dbus_bus_request_name()

  3. 进入监听循环:dbus_connection_read_write()

  4. 从总线上取出消息:dbus_connection_pop_message()

  5. 比对消息中的接口名和方法名:dbus_message_is_method_call()

  6. 如果比对一致,跳至相应处理,取出调用的参数+建立回传结果的通路:reply_to_method_call().

    注:回传动作本身等同于一次不需要等待结果的远程调用。

发送信号的流程:

主函数

  1. 建立一个dbus连接:dbus_bus_get()
  2. 为这个dbus连接(DbusConnection)起名:dbus_bus_request_name()
  3. 将总线设置为接收Glib事件循环:dbus_connection_setup_with_g_main (bus, NULL)
  4. 总指针设为参数,定时调用一次send()函数: g_timeout_add(time,send(),bus)
  5. 启动事件循环:g_main_loop_run (loop)

send()函数

  1. 建立一个发送信号的通道(包括信号的路径、接口名和信号名):dbus_message_new_signal()
  2. 将信号对应的相关参数压进去
    • 将参数附加到消息末尾:dbus_message_iter_init_append(DBusMessage * message, DBusMessageIter * iter )
    • 向消息追加基本类型值:dbus_message_iter_append_basic(DBusMessageIter * iter, int type, const void * value)
    • 在给定变量参数列表的消息中追加字段:dbus_message_append_args(DBusMessage * message, int first_arg_type, ... )
  3. 发送信号:dbus_connection_send(),dbus_connection_flush()
  4. 释放消息对象: dbus_message_unref (message)
  5. return TRUE;

进行一次远程调用的流程:

  1. 建立一个dbus连接:dbus_bus_get()
  2. 为这个dbus连接(DbusConnection)起名:dbus_bus_request_name()
  3. 申请一个远程调用通道(填入服务器名、接口名、调用名):dbus_message_new_method_call()
  4. 压入本次调用的参数:dbus_message_iter_init_append()dbus_message_iter_append_basic()dbus_message_append_args()
  5. 启动发送调用并释放相关的消息:dbus_connection_send_with_reply()
  6. 用dbus提供的函数提取参数的类型和参数:dbus_message_iter_init()dbus_message_iter_next()dbus_message_iter_get_arg_type()dbus_message_iter_get_basic()

信号接收流程:

main函数

  1. 建立一个dbus连接:dbus_bus_get()
  2. 为这个dbus连接(DbusConnection)起名:dbus_bus_request_name()
  3. 将总线设置为接收Glib事件循环:dbus_connection_setup_with_g_main (bus, NULL)
  4. 为将要进行的消息循环添加匹配条件(类型和接口):dbus_bus_add_match()
  5. 设置通知函数:dbus_connection_add_filter(connection, function, user_data, free_data_function )
  6. 运行这个事件循环:g_main_loop_run (loop)

通知函数

  1. 检测是否连接成功:dbus_message_is_signal(message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")
  2. 若连接成功,进入匹配判断:dbus_message_is_signal(message, Interface, Name)
  3. error初始化:dbus_error_init(&eror)
  4. 接收消息,并判断是否有错误:dbus_message_get_args (message, &error, DBUS_TYPE_STRING, the message address(&s), DBUS_TYPE_INVALID)
  5. 打印输出相应的结果:g_print()
  6. 根据结果返回HANDLER类型:
    • return DBUS_HANDLER_RESULT_HANDLED;
    • return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

D-Bus示例——通过会话总线收发信号

  • dbus-ping-send.c:每秒通过会话总线发送一个参数为字符串“Ping!”的信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <glib.h>
#include <dbus/dbus-glib.h>
static gboolean send_ping (DBusConnection *bus);
int main (int argc, char **argv)
{
GMainLoop *loop; //定义一个事件循环对象的指针
DBusConnection *bus; //定义总线连接对象的指针
DBusError error; //定义D-Bus错误消息对象
/* Create a new event loop to run in */
loop = g_main_loop_new (NULL, FALSE); //创建一个新的GMainLoop结构(新事件循环对象)
/* Get a connection to the session bus */
dbus_error_init (&error); //初始化DBusError结构
bus = dbus_bus_get (DBUS_BUS_SESSION, &error);//连接到总线
if (!bus) //判断连接是否正确
{
g_warning ("Failed to connect to the D-BUS daemon: %s", error.message); //使用Glib输出错误警告
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); //将总线设置为接收Glib事件循环
/* Every second call send_ping() with the bus as an argument*/
g_timeout_add (1000, (GSourceFunc)send_ping, bus);//总线指针设为参数,隔1000ms调用一次send_ping()函数
/* 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"); //创建具有对象路径和接口的新Ping信号
/* Append the string "Ping!" to the signal */
dbus_message_append_args (message,
DBUS_TYPE_STRING, "Ping!",
DBUS_TYPE_INVALID); //将字符串“Ping!”作为参数添加到信号中
/* 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!”作为参数添加到信号中并通过总线发送。在标准输出中会打印一条消息以让用户知道发送了 一个信号。

  • dbus-ping-listen.c:这个程序侦听dbus-ping-send.c正在发出的信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <glib.h>
#include <dbus/dbus-glib.h>
static DBusHandlerResult signal_filter
(DBusConnection *connection, DBusMessage *message, void *user_data); //定义接收消息函数的原型
int main (int argc, char **argv)
{
GMainLoop *loop; //定义一个事件循环对象的指针
DBusConnection *bus; //定义总线连接对象的指针
DBusError error; //定义D-Bus的错误消息对象
loop = g_main_loop_new (NULL, FALSE);//创建新事件循环对象
dbus_error_init (&error); //初始化DBusError结构
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); //将总线设为接收Glib事件循环
/* 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); //将 signal_filter设置为通知函数
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; //定义事件循环对象的指针,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 会被调用。不过,它也将会 收到来自总线本身的总线管理信号。要确定接收到消息时应该做些什么,仅仅需要检验消息头。如果消息是 总线断开信号,则事件循环终止,因为侦听一个不存在的总线是没有意义的。(告知总线信号已经处理)。 然后,将到来的消息与期望的消息相比较,如果成功,则解出参数并输出。如果到来的消息不是其中的任何一个, 则告知总线没有处理那个消息。

两种颜色的功德箱(逃