D-Bus 系列之入门

注解

本文整理自 D-Bus 学习系列文章

背景知识

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

典型的桌面都会有多个应用程序在运行,而且,它们经常需要彼此进行通信。DCOP 是一个用于 KDE 的解决方案,但是它依赖于 Qt,所以不能用于其他桌面环境之中。 类似的,Bonobo 是一个用于 GNOME 的解决方案,但是非常笨重,因为它是基于 CORBA 的。它还依赖于 GObject,所以也不能用于 GNOME 之外。 D-BUS 的目标是将 DCOP 和 Bonobo 替换为简单的 IPC,并集成这两种桌面环境。 由于尽可能地减少了 D-BUS 所需的依赖,所以其他可能会使用 D-BUS 的应用程序不用担心引入过多依赖。 相对于其它的 IPC, D-Bus 丢掉了一些不必要的、复杂的东西,也正是因为这个原因,D-Bus 比较快、简单。 D-Bus 不和低层的 IPC 直接竞争,比如 sockets, shared memory or message queues. 这些低层点的 IPC 有它们自己的特点,和 D-Bus 并不冲突。

什么是 D-Bus

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

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

在 D-Bus 中,“bus”是核心的概念,它是一个通道:不同的程序可以通过这个通道做些操作,比如方法调用、发送信号和监听特定的信号。

D-Bus 是低延迟而且低开销的,设计得小而高效,以便最小化传送的往返时间。另外,协议是二进制的,而不是文本的,这样就排除了费时的序列化过程。 从开发者的角度来看,D-BUS 是易于使用的。有线协议容易理解,客户机程序库以直观的方式对其进行包装。D-Bus 的主要目的是提供如下的一些更高层的功能:

  1. 结构化的名字空间
  2. 独立于架构的数据格式
  3. 支持消息中的大部分通用数据元素
  4. 带有异常处理的通用远程调用接口
  5. 支持广播类型的通信

Bus daemon 是一个特殊的进程:这个进程可以从一个进程传递消息给另外一个进程。 当然了,如果有很多 applications 链接到这个通道上,这个 daemon 进程就会把消息转发给这些链接的所有程序。 在最底层,D-Bus 只支持点对点的通信,一般使用本地套接字(AF_UNIX)在应用和 bus daemon 之间通信。 D-Bus 的点对点是经过 bus daemon 抽象过的,由 bus daemon 来完成寻址和发送消息,因此每个应用不必要关心要把消息发给哪个进程。 D-Bus 发送消息通常包含如下步骤(正常情况下):

  1. 创建和发送消息 给后台 bus daemon 进程,这个过程中会有两个上下文的切换
  2. 后台 bus daemon 进程会处理该消息,并转发给目标进程,这也会引起上下文的切换
  3. 目标程序接收到消息,然后根据消息的种类,做不同的响应:要么给个确认、要么应答、还有就是忽略它。最后一种情况对于“通知”类型的消息而言,前两种都会引起进一步的上 下文切换。

在一台机器上总线守护有多个实例 (instance)。这些总线之间都是相互独立的。

一个持久的系统总线(system bus)
它在引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序不能欺骗系统事件。 它是桌面会话和操作系统的通信,这里操作系统一般而言包括内核和系统守护进程。 这种通道的最常用的方面就是发送系统消息,比如:插入一个新的存储设备;有新的网络连接;等等。
还将有很多会话总线(session buses)
这些总线当用户登录后启动,属于那个用户私有。它是用户的应用程序用来通信的一个会话总线。 同一个桌面会话中两个桌面应用程序的通信,可使得桌面会话作为整体集成在一起以解决进程生命周期的相关问题。 这在 GNOME 和 KDE 桌面中大量使用。

综上原因,如果你准备在不同的进程之间传递大量的 数据,D-Bus 可能不是最有效的方法,最有效的方法是使用共享内存,但是对共享内存的管理也是相当复杂的。

D-Bus 编程经常涉及的一些概念

地址
连接建立有 server 和 client,对于 bus daemon,应用就是 client,daemon 是 server。 一个 D-Bus 的地址是指 server 用于监听,client 用于连接的地方,例如 unix:path=/tmp/abcedf 标识 server 将在路径 /tmp/abcedf 的 UNIX domain socket 监听。 地址可以是指定的 TCP/IP socket 或者其他在或者将在 D-Bus 协议中定义的传输方式。 如果使用 bus daemon,libdbus 将通过读取环境变量自动获取 session bus damon 的地址,通过检查一个指定的 UNIX domain socket 路径获取 system bus 的地址。如果使用 D-bus,但不是 daemon,需要定义那个应用是 server,那个是 client,并定义一套机制是他们认可 server 的地址,这不是通常的做法。
Bus Names 总线名字
当一个应用连接到 bus daemon,daemon 立即会分配一个名字给这个连接,称为 unique connection name, 这个唯一标识的名字以冒号:开头,例如:34-907,这个名字在 daemon 的整个生命周期是唯一的。 但是这种名字总是临时分配,无法确定的,也难以记忆,因此应用可以要求有另外一个名字 well-known name 来对应这个唯一标识,就像我们使用域名来对应 IP 地址一样。 例如可以使用 com.mycompany 来映射:34-907。应用程序可能会要求拥有额外的周知名字(well-known name)。 例如,你可以写一个规范来定义一个名字叫做 com.mycompany.TextEditor。 应用程序就可以发送消息到这个总线名字,对象,和接口以执行方法调用。 当一个应用结束或者崩溃是,OS kernel 会关闭它的总线连接。总线发送 notification 消息告诉其他应用,这个应用的名字已经失去他的 owner。 当检测到这类 notification 时,应用可以知道其他应用的生命周期。这种方式也可用于只有一个实例的应用,即不开启同样的两个应用的情况。
原生对象和对象路径
所有使用 D-BUS 的应用程序都包含一些对象,当经由一个 D-BUS 连接受到一条消息时,该消息是被发往一个对象而不是整个应用程序。 在开发中程序框架定义着这样的对象,例如 JAVA,GObject,QObject 等等,在 D-Bus 中成为 native object。 对于底层的 D-Bus 协议,即 libdbus API,并不理会这些 native object,它们使用的是一个叫做 object path 的概念。 通过 object path,高层编程可以为对象实例进行命名,并允许远程应用引用它们。 这些名字看起来像是文件系统路径,易读的路径名是受鼓励的做法,但也允许使用诸如“/com/mycompany/c5yo817y0c1y1c5b”等,只要它可以为你的应用程序所用。 Namespacing 的对象路径以开发者所有的域名开始(如 /org/kde)以避免系统不同代码模块互相干扰。 简单地说:一个应用创建对象实例进行 D-Bus 的通信,这些对象实例都有一个名字,命名方式类似于路径, 例如 /com/mycompany,这个名字在全局(session 或者 system)是唯一的,用于消息的路由。
Proxies 代理
代理对象用来表示其他远程的 remote object。 当我们触发了 proxy 对象的 method 时,将会在 D-Bus 上发送一个 method_call 的消息,并等待答复,根据答复返回。使用非常方便,就像调用一个本地的对象。
接口 Interface
每一个对象支持一个或者多个接口,接口是一组方法和信号,接口定义一个对象实体的类型。 D-Bus 对接口的命名方式,类似 org.freedesktop.Introspectable。开发人员通常将使用编程语言类的的名字作为接口名字。
方法和信号 Methods and Signals
每一个对象有两类成员:方法和信号。方法就是 JAVA 中同样概念,方法是一段函数代码,带有输入和输出。信号是广播给所有兴趣的其他实体,信号可以带有数据 payload。 在 D-BUS 中有四种类型的消息:方法调用(method calls)、方法返回(method returns)、信号(signals)和错误(errors)。 要执行 D-BUS 对象的方法,您需要向对象发送一个方法调用消息。 它将完成一些处理(就是执行了对象中的 Method,Method 是可以带有输入参数的。)并返回,返回消息或者错误消息。 信号的不同之处在于它们不返回任何内容:既没有“信号返回”消息,也没有任何类型的错误消息。

通过上面的描述,我们可以获得下面的视图::

Address –> [Bus Name] –> Path –> Interface –> Method

bus name 不是必要的,它只在 daemon 的情况下用于路由,点对点的直接连接是不需要的。 简单地说 :Address 是 D-Bus 中 server 用来监听 client 的地址,当一个 client 连接上 D-Bus,通常是 Daemo 的方式,这个 client 就有了一个 Bus Name。 其他应用可以根据消息中所带的 Bus Name,来判断和哪个应用相关。 消息在总线中传递的时候,传递到应用中,再根据 object path,送至应用中具体的对象实例中,也就是是应用中根据 Interface 创建的对象。 这些 Interface 有 method 和 singal 两种,用来发送、接收、响应消息。

这些概念对初学者可能会有些混淆,但是通过后面学习一个小程序,就很清楚,这是后面一个例子的示意图,回过头来看看之前写的这篇文章,这个示意图或许会更清楚。

../../_images/dbus-example.png

D-Bus 消息类型

消息通过 D-Bus 在进程间传递。有四类消息: a. Method call 消息:将触发对象的一个 method #. Method return 消息:触发的方法返回的结果 #. Error 消息:触发的方法返回一个异常 #. Signal 消息:通知,可以看作为事件消息。

一个消息有消息头 header,里面有 field,有一个消息体 body,里面有参数 arguments。消息头包含消息体的路由信息,消息体就是净荷 payload。头字段可能包括发送者的 bus 名,目的地的 bus 名,方法或者 signal 名等等,其中一个头字段是用于描述 body 中的参数的类型,例如“i”标识 32 位整数,”ii”表示净荷为 2 个 32 为整数。

发送 Method call 消息的场景

一个 method call 消息从进程 A 到进程 B,B 将应答一个 method return 消息或者 error 消息。 在每个 call 消息带有一个序列号,应答消息也包含同样的号码,使之可以对应起来。 他们的处理过程如下:

  1. 如果提供 proxy,通过触发本地一个对象的方法从而触发另一个进程的远端对象的方法。应用调用 proxy 的一个方法,proxy 构造一个 method call 消息发送到远端进程。
  2. 对于底层的 API,不使用 proxy,应用需要自己构造 method call 消息。
  3. 一个 method call 消息包含:远端进程的 bus name,方法名字,方法的参数,远端进程中 object path,可选的接口名字。
  4. method call 消息发送到 bus daemon
  5. bus daemon 查看目的地的 bus name。如果一个进程对应这个名字,bus daemon 将 method call 消息发送到该进程中。如果没有发现匹配,bus daemon 创建一个 error 消息作为应答返回。
  6. 进程接收后将 method call 消息分拆。对于简单的底层 API 情况,将立即执行方法,并发送一个 method reply 消息给 bus daemon。对于高层的 API,将检查对象 path,interface 和 method,触发一个 native object 的方法,并将返回值封装在一个 method reply 消息中。
  7. bus daemon 收到 method reply 消息,将其转发到原来的进程中
  8. 进程查看 method reply 消息,获取返回值。这个响应也可以标识一个 error 的残生。当使用高级的捆绑,method reply 消息将转换为 proxy 方法的返回值或者一个 exception。

Bus daemon 保证 message 的顺序,不会乱序。例如我们发送两个 method call 消息到同一个接受方,他们将按顺序接受。接收方并不要求一定按顺序回复。消息有一个序列号了匹配收发消息。

发送 Signal 的场景

signal 是个广播的消息,不需要响应,接收方向 daemon 注册匹配的条件,包括发送方和信号名,bus 守护只将信号发送给希望接受的进程。

处理流程如下:

  1. 一个 signal 消息发送到 bus daemon。
  2. signal 消息包含发布该信号的 interface 名字,signal 的名字,进程的 bus 名字,以及参数。
  3. 任何进程都可以注册的匹配条件(match rules) 表明它所感兴趣的 signal。总线有个注册 match rules 列表。
  4. bus daemon 检查那些进程对该信号有兴趣,将信号消息发送到这些进程中。
  5. 收到信号的进程决定如何处理。如果使用高层的捆绑,一个 porxy 对象将会释放一个 native 的信号。如果使用底层的 API,进程需要检查信号的发送方和信号的名字决定如何进行处理。

Introspection   D-Bus 对象可能支持一个接口 org.freedesktop.DBus.Introspectable。该接口有一个方法 Introspect,不带参数,将返回一个 XML string。这个 XML 字符串描述接口,方法,信号。

简单的例子

D-Bus 类型和 GType 的映射

在 D-Bus 编程中,基础类型和 GType 的映射表格如下。在后面的程序小例子中我们会看到具体如何对应。

../../_images/dbus-map-gtype.png

在 D-Bus 编程中,container 类型和 GType 的映射表格如下:

../../_images/dbus-container-gtype.png

在下面的例子中,使用了 dbus-1 dbus-glib-1 glib-2.0。Makefile 的如下:

 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
CC=gcc

CFLAGS += `pkg-config --cflags dbus-glib-1 glib-2.0`
LIBS += `pkg-config --libs dbus-glib-1 glib-2.0`

all: sync_sample async_sample signal_send signal_recv method_service method_call

async_sample: async_sample.c
	$(CC) -Wall -g $(CFLAGS) $< $(LIBS) -o $@

sync_sample: sync_sample.c
	$(CC) -Wall -g $(CFLAGS) $< $(LIBS) -o $@

signal_send: signal_send.c
	$(CC) -Wall -g $(CFLAGS) $< $(LIBS) -o $@

signal_recv: signal_recv.c
	$(CC) -Wall -g $(CFLAGS) $< $(LIBS) -o $@

method_service: method_service.c
	$(CC) -Wall -g $(CFLAGS) $< $(LIBS) -o $@

method_call:method_call.c
	$(CC) -Wall -g $(CFLAGS) $< $(LIBS) -o $@

clean:
	rm -f sync_sample async_sample signal_send signal_recv method_service method_call

同步的例子

同步即程序发出 method call 消息,等待 method_return 消息。下面是一个小例子,如果我们用 dbus-send 命令,可以使用:

dbus-send --session --print-reply --type=method_call --dest=org.freedesktop.Notifications / org.freedesktop.DBus.Introspectable.Introspect
 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
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus-glib.h>

int main(int argc, char **argv)
{
    GError *error;
    DBusGConnection *conn;
    DBusGProxy *proxy;
    char *str;

    /* gtype init */
    error = NULL;
    /* connect session bus with dbus_g_get, or DBUS_BUS_SYSTEM connect system bus */
    conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
    if (conn == NULL) {
        g_printerr("Failed to open connection to bus:%s\n", error->message);
        g_error_free(error);
        exit(1);
    }

    /* create a proxy object for  */
    proxy = dbus_g_proxy_new_for_name(conn,
            "org.freedesktop.Notifications", /* service */
            "/", /* path */
            "org.freedesktop.DBus.Introspectable"/* interface */
            );
    error = NULL;
    if (!dbus_g_proxy_call(proxy, "Introspect", &error, G_TYPE_INVALID, G_TYPE_STRING, &str, G_TYPE_INVALID)) {
        if (error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION) {
            g_printerr("Caught remote method exception:%s-%s\n", dbus_g_error_get_name(error), error->message);
        } else {
            g_printerr("Error:%s\n", error->message);
        }
        g_error_free(error);
        exit(1);
    }

    g_print("Message Method return from bus:\n%s\n", str);
    g_free(str);
    g_object_unref(proxy);
    exit(1);

    return 0;
}

异步的例子

异步中,程序将不等返回消息,继续执行,等有返回消息的时候,触发一个回调函数 。下面是同样的操作,但是用异步的方式来实现:

 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
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus-glib.h>

static void callback_func(DBusGProxy *proxy, DBusGProxyCall *call_id, void *user_data)
{
    GError *err = NULL;
    gchar *str = NULL;
    GMainLoop *main_loop = user_data;

    /* 结束一条消息的收发,处理收到的消息 */
    dbus_g_proxy_end_call(proxy, call_id, &err, G_TYPE_STRING, &str, G_TYPE_INVALID);
    if (err != NULL) {
        g_print("Error in method call:%s\n", err->message);
        g_error_free(err);
    } else {
        g_print("Success, message:\n%s\n", str);
    }

    g_main_loop_quit(main_loop);
}

int main(int argc, char **argv)
{
    GError *error;
    DBusGConnection *conn;
    DBusGProxy *proxy;
    GMainLoop *main_loop;

    error = NULL;
    main_loop = g_main_loop_new(NULL, FALSE);
    /* connect session bus with dbus_g_get, or DBUS_BUS_SYSTEM connect system bus */
    conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
    if (conn == NULL) {
        g_printerr("Failed to open connection to bus:%s\n", error->message);
        g_error_free(error);
        exit(1);
    }

    /* create a proxy object for  */
    proxy = dbus_g_proxy_new_for_name(conn,
            "org.freedesktop.Notifications", /* service */
            "/", /* path */
            "org.freedesktop.DBus.Introspectable"/* interface */
            );
    error = NULL;
    dbus_g_proxy_begin_call(proxy, "Introspect", callback_func, main_loop, NULL, G_TYPE_INVALID);
    g_main_loop_run(main_loop);

    return 0;
}

Signal 的收发的例子

现在从底层,即 libdbus 学习如何发送 signal,以及如何监听 signal。signal 在 D-Bus 的 Daemon 中广播,为了提高效率,只发送给向 daemon 注册要求该 singal 的对象。 程序,第一步需要将应用和 D-Bus 后台建立连接,也就是和 System D-Bus daemon 或者 Session D-Bus daemon 建立连接。 一旦建立,daemon 会给这条连接分配一个名字,这个名字在 system 或者 session 的生命周期是唯一的, 即 unique connection name,为了方便记忆,可以为这条连接分配一个便于记忆的 well-known name。 对于信号方式,分配这个名字不是必须的(在 method_call 中是需要的,我们在下一次学习中谈到),因为在信号的监听中秩序给出 Interface 的名字和信号名称。 在下面的例子中,可以将相关的代码屏蔽掉,不影响运行,但是通常我们都这样处理,尤其在复杂的程序中。

在我们的例子中,定义这个 BUS name 为 test.singal.source。当然一个好的名字,为了避免于其他应用重复,应当使用 com.mycompany.myfunction 之类的名字。 而 interface 的名字,一般前面和 connection 的 BUS name 一致。

发送方的程序

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>

int send_a_signal( char * sigvalue)
{
    DBusError err;
    DBusConnection * connection;
    DBusMessage * msg;
    DBusMessageIter arg;
    dbus_uint32_t  serial = 0;
    int ret;

    //步骤1:建立与D-Bus后台的连接
    /* initialise the erroes */
    dbus_error_init(&err);
    /* Connect to Bus*/
    connection = dbus_bus_get(DBUS_BUS_SESSION , &err );
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Connection Err : %s\n",err.message);
        dbus_error_free(&err);
    }
    if(connection == NULL) {
        return -1;
    }

    //步骤2:给连接名分配一个well-known的名字作为Bus name,这个步骤不是必须的,可以用if 0来注释着一段代码,我们可以用这个名字来检查,是否已经开启了这个应用的另外的进程。
#if 1
    ret = dbus_bus_request_name(connection,"test.singal.source",DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Name Err : %s\n",err.message);
        dbus_error_free(&err);
    }
    if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
        return -1;
#endif

    //步骤3:发送一个信号
    //根据图,我们给出这个信号的路径(即可以指向对象),接口,以及信号名,创建一个Message
    if((msg = dbus_message_new_signal ("/test/signal/Object","test.signal.Type","Test")) == NULL) {
        fprintf(stderr,"Message NULL\n");
        return -1;
    }
    //给这个信号(messge)具体的内容
    dbus_message_iter_init_append (msg,&arg);
    if(!dbus_message_iter_append_basic (&arg,DBUS_TYPE_STRING,&sigvalue)) {
        fprintf(stderr,"Out Of Memory!\n");
        return -1;
    }

    //步骤4: 将信号从连接中发送
    if( !dbus_connection_send(connection,msg,&serial)) {
        fprintf(stderr,"Out of Memory!\n");
        return -1;
    }
    dbus_connection_flush (connection);
    printf("Signal Send\n");

    //步骤5: 释放相关的分配的内存。
    dbus_message_unref(msg);
    return 0;
}


int main( int argc , char ** argv)
{
    send_a_signal("Hello,world!");
    return 0;
}

接收该信号的的例子

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>

void listen_signal()
{
    DBusMessage * msg;
    DBusMessageIter arg;
    DBusConnection * connection;
    DBusError err;
    int ret;
    char * sigvalue;

    //步骤1:建立与D-Bus后台的连接
    dbus_error_init(&err);
    connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Connection Error %s\n",err.message);
        dbus_error_free(&err);
    }
    if(connection == NULL) {
        return;
    }

    //步骤2:给连接名分配一个可记忆名字test.singal.dest作为Bus name,这个步骤不是必须的,但推荐这样处理
    ret = dbus_bus_request_name(connection,"test.singal.dest",DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Name Error %s\n",err.message);
        dbus_error_free(&err);
    }
    if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
        return;
    }

    //步骤3:通知D-Bus daemon,希望监听来行接口test.signal.Type的信号
    dbus_bus_add_match(connection,"type='signal',interface='test.signal.Type'",&err);
    //实际需要发送东西给daemon来通知希望监听的内容,所以需要flush
    dbus_connection_flush(connection);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Match Error %s\n",err.message);
        dbus_error_free(&err);
    }

    //步骤4:在循环中监听,每隔开1秒,就去试图自己的连接中获取这个信号。这里给出的是从连接中获取任何消息的方式,所以获取后去检查一下这个消息是否我们期望的信号,并获取内容。我们也可以通过这个方式来获取method call消息。
    while(1) {
        dbus_connection_read_write(connection,0);
        msg = dbus_connection_pop_message (connection);
        if(msg == NULL){
            sleep(1);
            continue;
        }

        if(dbus_message_is_signal(msg,"test.signal.Type","Test") ){
            if(!dbus_message_iter_init(msg,&arg)) {
                fprintf(stderr,"Message Has no Param");
            } else if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING) {
                g_printerr("Param is not string");
            } else {
                dbus_message_iter_get_basic(&arg,&sigvalue);
                printf("Got Singal with value : %s\n",sigvalue);
            }
        }
        dbus_message_unref(msg);
    }//End of while

    return ;
}

int main( int argc , char ** argv){
    listen_signal();
    return 0;
}

Method 的收发小例子

现在我们从底层,即 libdbus 学习如何发送 Method 以及如何等待应答,在之前的代码中,我们给出了同步方式的代码 ,这是更为高层的处理方式,建议使用。 监听 method 和监听 signal 的方式非常相似。在给出例子之前,我希望和上次学习一样给出一个示意图,更好地了解 D-Bus 的各个概念。

../../_images/method_example.png

在下面的例子中,我们将学习如何在消息中加入多个参数的情况。

方法监听的例子

Method 的监听和 signal 的监听的处理时一样,但是信号是不需要答复,而 Method 需要。

  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
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>

/*读取消息的参数,并且返回两个参数,一个是bool值stat,一个是整数level*/
void reply_to_method_call(DBusMessage * msg, DBusConnection * conn)
{
    DBusMessage * reply;
    DBusMessageIter arg;
    char * param = NULL;
    dbus_bool_t stat = TRUE;
    dbus_uint32_t level = 2010;
    dbus_uint32_t serial = 0;

    //从msg中读取参数,这个在上一次学习中学过
    if(!dbus_message_iter_init(msg,&arg)) {
        printf("Message has no args/n");
    } else if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING) {
        printf("Arg is not string!/n");
    } else {
        dbus_message_iter_get_basic(&arg,& param);
    }
    if(param == NULL) return;

    //创建返回消息reply
    reply = dbus_message_new_method_return(msg);
    //在返回消息中填入两个参数,和信号加入参数的方式是一样的。这次我们将加入两个参数。
    dbus_message_iter_init_append(reply,&arg);
    if(!dbus_message_iter_append_basic(&arg,DBUS_TYPE_BOOLEAN,&stat)) {
        printf("Out of Memory!/n");
        exit(1);
    }
    if(!dbus_message_iter_append_basic(&arg,DBUS_TYPE_UINT32,&level)) {
        printf("Out of Memory!/n");
        exit(1);
    }
    //发送返回消息
    if( !dbus_connection_send(conn, reply, &serial)){
        printf("Out of Memory/n");
        exit(1);
    }
    dbus_connection_flush (conn);
    dbus_message_unref (reply);
}

/* 监听D-Bus消息,我们在上次的例子中进行修改 */
void listen_dbus()
{
    DBusMessage * msg;
    DBusMessageIter arg;
    DBusConnection * connection;
    DBusError err;
    int ret;
    char * sigvalue;

    dbus_error_init(&err);
    //创建于session D-Bus的连接
    connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
    if(dbus_error_is_set(&err)){
        fprintf(stderr,"Connection Error %s/n",err.message);
        dbus_error_free(&err);
    }
    if(connection == NULL) {
        return;
    }
    //设置一个BUS name:test.wei.dest
    ret = dbus_bus_request_name(connection,"test.wei.dest",DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Name Error %s/n",err.message);
        dbus_error_free(&err);
    }
    if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
        return;
    }

    //要求监听某个singal:来自接口test.signal.Type的信号
    dbus_bus_add_match(connection,"type='signal',interface='test.signal.Type'",&err);
    dbus_connection_flush(connection);
    if(dbus_error_is_set(&err)){
        fprintf(stderr,"Match Error %s/n",err.message);
        dbus_error_free(&err);
    }

    while(TRUE){
        dbus_connection_read_write (connection,0);
        msg = dbus_connection_pop_message (connection);

        if(msg == NULL){
            sleep(1);
            continue;
        }

        if(dbus_message_is_signal(msg,"test.signal.Type","Test")){
            if(!dbus_message_iter_init(msg,&arg)) {
                fprintf(stderr,"Message Has no Param");
            } else if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING) {
                g_printerr("Param is not string");
            } else {
                dbus_message_iter_get_basic(&arg,&sigvalue);
                printf("Got Singal with value : %s\n",sigvalue);
            }
        }else if(dbus_message_is_method_call(msg,"test.method.Type","Method")){
            //我们这里面先比较了接口名字和方法名字,实际上应当现比较路径
            if(strcmp(dbus_message_get_path (msg),"/test/method/Object") == 0) {
                reply_to_method_call(msg, connection);
            }
        }
        dbus_message_unref(msg);
    }
}

int main( int argc , char ** argv)
{
    listen_dbus();
    return 0;
}

方法调用的例子

  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
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>

//建立与session D-Bus daemo的连接,并设定连接的名字,相关的代码已经多次使用过了
DBusConnection *  connect_dbus()
{
    DBusError err;
    DBusConnection * connection;
    int ret;

    //Step 1: connecting session bus
    /* initialise the erroes */
    dbus_error_init(&err);
    /* Connect to Bus*/
    connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
    if(dbus_error_is_set(&err)){
        fprintf(stderr,"Connection Err : %s\n",err.message);
        dbus_error_free(&err);
    }
    if(connection == NULL) {
        return NULL;
    }

    //step 2: 设置BUS name,也即连接的名字。
    ret = dbus_bus_request_name(connection,"test.wei.source",DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr,"Name Err : %s\n",err.message);
        dbus_error_free(&err);
    }

    if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
        return NULL;
    }

    return connection;
}

void send_a_method_call(DBusConnection * connection,char * param)
{
    DBusError err;
    DBusMessage * msg;
    DBusMessageIter    arg;
    DBusPendingCall * pending;
    dbus_bool_t * stat;
    dbus_uint32_t * level;

    dbus_error_init(&err);

    //针对目的地地址,请参考图,创建一个method call消息。 Constructs a new message to invoke a method on a remote object.
    msg = dbus_message_new_method_call ("test.wei.dest","/test/method/Object","test.method.Type","Method");
    if(msg == NULL) {
        g_printerr("Message NULL");
        return;
    }

    //为消息添加参数。Append arguments
    dbus_message_iter_init_append(msg, &arg);
    if(!dbus_message_iter_append_basic (&arg, DBUS_TYPE_STRING,&param)) {
        g_printerr("Out of Memory!");
        exit(1);
    }

    //发送消息并获得reply的handle 。Queues a message to send, as with dbus_connection_send() , but also returns a DBusPendingCall used to receive a reply to the message.
    if(!dbus_connection_send_with_reply(connection, msg,&pending, -1)){
        g_printerr("Out of Memory!");
        exit(1);
    }

    if(pending == NULL) {
        g_printerr("Pending Call NULL: connection is disconnected ");
        dbus_message_unref(msg);
        return;
    }

    dbus_connection_flush(connection);
    dbus_message_unref(msg);

    //waiting a reply,在发送的时候,已经获取了method reply的handle,类型为DBusPendingCall。
    // block until we recieve a reply, Block until the pending call is completed.
    dbus_pending_call_block (pending);
    // get the reply message,Gets the reply, or returns NULL if none has been received yet.
    msg = dbus_pending_call_steal_reply (pending);
    if (msg == NULL) {
        fprintf(stderr, "Reply Null\n");
         exit(1);
    }
     // free the pending message handle
     dbus_pending_call_unref(pending);
    // read the parameters
    if (!dbus_message_iter_init(msg, &arg)) {
        fprintf(stderr, "Message has no arguments!\n");
    } else if ( dbus_message_iter_get_arg_type (&arg) != DBUS_TYPE_BOOLEAN) {
        fprintf(stderr, "Argument is not boolean!\n");
    } else {
        dbus_message_iter_get_basic (&arg, &stat);
    }

    if (!dbus_message_iter_next(&arg)) {
        fprintf(stderr, "Message has too few arguments!\n");
    } else if ( dbus_message_iter_get_arg_type (&arg) != DBUS_TYPE_UINT32 ) {
        fprintf(stderr, "Argument is not int!\n");
    } else {
        dbus_message_iter_get_basic (&arg, &level);
    }

    printf("Got Reply: %d, %d\n", (int)stat, (int)level);
    dbus_message_unref(msg);
}

int main( int argc , char ** argv)
{
    DBusConnection * connection;

    connection = connect_dbus();
    if(connection == NULL) {
        return -1;
    }

    send_a_method_call(connection,"Hello, D-Bus");
    return 0;
}

使用 xml 定义 D-Bus 接口

在 D-Bus 中,可以将 D-Bus 接口定义用 XML 格式表述处理,并利用工具,自动生成头文件,给出工整的调用方式。

下面是一个 XML 的例子。

客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>

<node name="/com/wei/MyObject">
    <interface name="com.wei.MyObject.Sample">
        <method name="Test">
            <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="wei_response" /> 
            <arg type="u" name="x" direction="in" />
            <arg type="d" name="d_ret" direction="out" />
        </method>
    </interface>
</node>

dbus-binding-tool --mode=glib-client --prefix=com_wei wei.xml > wei_client.h

服务端

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<node name="/com/wei/MyObject">
    <interface name="com.wei.MyObject.Sample">
        <method name="Test">
            <arg name="x" type="u" direction="in" />
            <arg name="d_ret" type="d" direction="out" />
        </method>
    </interface>
</node>
dbus-binding-tool --mode=glib-server --prefix=com_wei wei.xml > wei_server.h