一、D-Bus简介

1. D-Bus是什么

D-Bus最主要的用途是在 Linux 桌面环境为进程提供通信,同时能将 Linux 桌面环境和 Linux 内核事件作为消息传递到进程。D-Bus(其中D原先是代表桌面“Desktop” 的意思),即:用于桌面操作系统的通信总线。

D-Bus的主要概念为总线,注册后的进程可通过总线接收或传递消息,进程也可注册后等待内核事件响应,例如等待网络状态的转变或者计算机发出关机指令。

D-Bus是为Linux系统开发的进程间通信(IPC)和远程过程调用(RPC)机制,使用统一的通信协议来代替现有的各种IPC解决方案。D-Bus允许系统级进程(如:打印机和硬件驱动服务)和普通用户进程进行通信。

2. D-Bus特性

D-Bus使用一个快速的二进制消息传递协议,D-Bus协议的低延迟和低消耗特点适用于同一台机器的通信。D-Bus的规范目前由freedesktop.org项目定义,可供所有团体使用。

D-Bus不和低层的IPC直接竞争,比如sockets,shared memory或message queues。低层IPC有自己的特点,和D-Bus并不冲突。

与其他重量级的进程间通信技术不同,D-Bus是非事务的。D-Bus使用了状态以及连接的概念,比UDP等底层消息传输协议更“聪明”。但另一方面,D-Bus传送的是离散消息,与TCP协议将数据看做“流”有所不同。D-Bus支持点对点的消息传递以及广播/订阅式的通信。

总结如下:

1、D-BUS的协议是低延迟而且低开销的,设计小巧且高效,以便最小化传送时间。从设计上避免往返交互并允许异步操作。
2、协议是二进制的,而不是文本,排除序列化过程。
3、考虑了字节序问题。
4、易用性:按照消息而不是字节流来工作,并且自动地处理了许多困难的IPC问题,并且D-Bus库以可以封装的方式来设计,开发者可以使用框架里存在的对象/类型系统,而不用学习一种新的专用于IPC的对象/类型系统。
5、请求时启动服务以及安全策略。
6、支持多语言(C/C++/Java/C#/Python/Ruby),多平台(Linux/windows/maemo)。
7、采用C语言,而不是C++。 H、由于基本上不用于internet上的IPC,因此对本地IPC进行了特别优化。
8、提供服务注册,理论上可以进行无限扩展。
9、支持广播类型的通信。
10、带有异常处理的通用远程调用接口;

二、D-Bus架构

1. 结构层次

D-Bus进程间通信主要有三层架构:
1.底层接口层:主要是通过libdbus这个函数库,给予系统使用DBus的能力。

2.总线层:主 要Message bus daemon这个总线守护进程提供的,在Linux系统启动时运行,负责进程间的消息路由和传递,其中包括Linux内核和Linux桌面环境的消息传递。总线守护进程可同时与多个应用程序相连,并能把来自一个应用程序的消息路由到0或者多个其他程序。

3.应用封装层:通过一系列基于特定应用程序框架将DBus的底层接口封装成友好的Wrapper库,供不同开发人员使用。比如libdbus-glib, libdbus-python.

2.系统总线和会话总线

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

  • 一个持久的系统总线(system bus)

    它在引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序不能欺骗系统事件。 它是桌面会话和操作系统的通信,这里操作系统一般而言包括内核和系统守护进程。 这种通道的最常用的方面就是发送系统消息,比如:插入一个新的存储设备;有新的网络连接;等等。

  • 还将有很多会话总线(session buses)‘
    普通进程创建,可同时存在多条。会话总线属于某个进程私有,它用于进程间传递消息。

三、D-Bus编程基础知识

1.address地址

使用d-bus的应用程序既可以是server也可以是client,server监听到来的连接,client连接到 server,一旦连接建立,消息就可以流转。点对点通信时就是一个 server 和 一个 client;如果使用dbus daemon,所有的应用程序都是client,bus daemon 是server,daemon监听所有的连接,应用程序初始化连接到daemon

dbus地址指明server将要监听的地方,client将要连接的地方,例如,地址:unix:path=/tmp/abcdef表明 server将在/tmp/abcdef路径下监听unix域的socket,client也将连接到这个socket。一个地址也可以指明是 TCP/IP的socket,或者是其他的。

当使用bus daemon时,libdbus会从环境变量中(DBUS_SESSION_BUS_ADDRESS)自动认识“会话daemon”的地址。如果是系统 daemon,它会检查指定的socket路径获得地址,也可以使用环境变量(DBUS_SESSION_BUS_ADDRESS)进行设定。

2. bus name 总线名字

当一个应用连接到 bus daemon,daemon 立即会分配一个名字给这个连接,称为 Unique Connection Name, 这个唯一标识的名字以冒号 “:” 开头,例如 :1.2,这个名字在 daemon 的整个生命周期是唯一的。

但是这种名字总是临时分配,无法确定的,也难以记忆,因此应用可以要求有另外一个公共名 well-known name 来对应这个唯一标识,就像我们使用域名来映射 IP地址一样。例如可以使用 org.fmddlmyy.Test 来映射 :1.2。这样我们就可以使用公共名连接到 DBus 服务。

3. 原生对象和对象路径

d-bus 的底层接口是没有这些对象的概念的,它提供的是一种叫对象路径(object path),用于让高层接口绑定到各个对象中去,允许远端应用程序指向它们。object path就像是一个文件路径,可以叫做 /org/kde/kspread/sheets/3/cells/4/5 等。

4. 接口 Interface

接口是一组方法和信号,每一个对象支持一个或者多个接口,接口定义一个对象实体的类型。 D-Bus使用简单的命名空间字符串来表示接口,例如 org.freedesktop.Introspectable。

5. Methods 和 Signals

每一个对象有两类成员:方法和信号:

  • 方法就是一个函数,具有有输入和输出;
  • 信号会被广播,感兴趣的对象可以处理这个 信号,同时信号中也可以带有相关的数据。

在 D-BUS 中有四种类型的消息:

1、方法调用(method call) # 在对象上执行一个方法
2、方法返回(method return) # 返回方法执行的结果
3、信号(signal) # 调用方法产生的异常
4、错误(error)# 通知指定的信号发生了,可以想象成“事件”。

要执行D-BUS对象的方法,您需要向对象发送一个方法调用消息。 它将完成一些处理(就是执行了对象中的Method,Method是可以带有输入参数的)并返回,返回消息或者错误消息。 信号的不同之处在于它们不返回任何内容:既没有“信号返回”消息,也没有任何类型的错误消息。

6. 总结:方法所需要的参数

要在指定的对象中调用指定的方法,需要知道的参数如下:
Address -> [Bus Name] -> Path -> Interface -> Method

bus name是可选的,除非是希望把消息送到特定的应用中才需要。interface也是可选的,有一些历史原因,DCOP不需要指定接口,因为DCOP在同一个对象中禁止同名的方法。

四、D-Bus运行环境

1. 运行环境安装

# 安装依赖
sudo apt-get install dbus
sudo apt-get install libgtk2.0-dev
sudo apt-get install libdbus-glib-1-dev
# 安装 D-Feet
sudo apt-get install d-feet

同时下载一个简单的 DBus 程序:链接,运行方法:

tar -zxvf hello-dbus3-0.1.tar.gz

# 编译
cd hello-dbus3-0.1/
./autogen.sh
./configure
make

# 运行
cd src
./example-service

运行 d-feet,打开 Session bus,找到 “org.fmddlmyy.Test” 连接名,这个链接就是我们刚刚运行的一个D-Bus程序:

在这里插入图片描述

在右侧展开栏我们会发现 org.fmddlmyy.Test.Basic 下有一个 Add 方法,我们点击它,输入 1,2,点击执行,可以看到给我们返回了结果:

在这里插入图片描述

通过上面的操作我们通过 d-feet 发起了一次 d-bus 请求。

2. dbus-send以及dbus-monitor

dbus提供了两个小工具:dbus-send和dbus-monitor。我们可以用dbus-send发送消息。用dbus-monitor监视总线上流动的消息。 让我们通过dbus-send发送消息来调用前面的Add方法,这时dbus-send充当了应用程序B。用dbus-monitor观察调用过程中的消息

保持上面的example-server运行,新建两个窗口一个运行dubs-monitor一个运行如下命令

dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.fmddlmyy.Test.Basic.Add int32:100 int32:999
  • dbus-send的详细用法可以参阅手册。调用远程方法的一般形式是

    $ dbus-send [--system | --session] --type=method_call --print-reply --dest=连接名 对象路径 接口名.方法名 参数类型:参数值 参数类型:参数值
    

    dbus-send支持的参数类型包括:string, int32, uint32, double, byte, boolea

dubs-monitor窗口返回的消息如下:

# 1、向 D-Bus Damon 发送Hello建立连接
method call time=1653831830.713656 sender=:1.97 -> destination=org.freedesktop.DBus serial=1 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello
method return time=1653831830.713685 sender=org.freedesktop.DBus -> destination=:1.97 serial=1 reply_serial=1
   string ":1.97"
# 2、广播全局,指定名称的拥有者发生了变化 
signal time=1653831830.713696 sender=org.freedesktop.DBus -> destination=(null destination) serial=10 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.97"  # 名字
   string ""       # 拥有者原始名字
   string ":1.97"  # 拥有者现在名字
# 3、通知应用获得了指定名称的拥有权
signal time=1653831830.713712 sender=org.freedesktop.DBus -> destination=:1.97 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
   string ":1.97"
# 4、调用 Add 方法  
method call time=1653831830.714160 sender=:1.97 -> destination=org.fmddlmyy.Test serial=2 path=/TestObj; interface=org.fmddlmyy.Test.Basic; member=Add
   int32 123
   int32 456
# 5、Add 方法返回结果
method return time=1653831830.714310 sender=:1.95 -> destination=:1.97 serial=7 reply_serial=2
   int32 579
# 6、通知应用失去了指定名称的拥有权
signal time=1653831830.715690 sender=org.freedesktop.DBus -> destination=:1.97 serial=6 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameLost
   string ":1.97"
# 7、广播全局,指定名称的拥有者发生了变化
signal time=1653831830.715717 sender=org.freedesktop.DBus -> destination=(null destination) serial=11 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.97"
   string ":1.97"
   string ""

上述一次完整的 d-bus 通信的流程

3.Dbus通信流程总结

(1) 方法调用的一般流程:

1.使用不同语言绑定的dbus高层接口,都提供了一些代理对象,调用其他进程里面的远端对象就像是在本地进程中的调用一样。应用调用代理上的方法,代理将构造一个方法调用消息给远端的进程。
2.在DBUS的底层接口中,应用需要自己构造方法调用消息(method call message),而不能使用代理。
3.方法调用消息里面的内容有:目的进程的bus name,方法的名字,方法的参数,目的进程的对象路径,以及可选的接口名称。
4.方法调用消息是发送到bus daemon中的。
5.bus daemon查找目标的bus name,如果找到,就把这个方法发送到该进程中,否则,daemon会产生错误消息,作为应答消息给发送进程。
6. 目标进程解开消息,在dbus底层接口中,会立即调用方法,然后发送方法的应答消息给daemon。在dbus高层接口中,会先检测对象路径,接口, 方法名称,然后把它转换成对应的对象(如GObject,QT中的QObject等)的方法,然后再将应答结果转换成应答消息发给daemon。
7.bus daemon接受到应答消息,将把应答消息直接发给发出调用消息的进程。
8.应答消息中可以包容很多返回值,也可以标识一个错误发生,当使用绑定时,应答消息将转换为代理对象的返回值,或者进入异常

(2) 信号的一般流程:

1.当使用dbus底层接口时,信号需要应用自己创建和发送到daemon,使用dbus高层接口时,可以使用相关对象进行发送,如Glib里面提供的信号触发机制。
2.信号包含的内容有:信号的接口名称,信号名称,发送进程的bus name,以及其他参数。
3.任何进程都可以依据”match rules”注册相关的信号,daemon有一张注册的列表。
4.daemon检测信号,决定哪些进程对这个信号感兴趣,然后把信号发送给这些进程。
5.每个进程收到信号后,如果是使用了dbus高层接口,可以选择触发代理对象上的信号。如果是dbus底层接口,需要检查发送者名称和信号名称,然后决定怎么做。

五、DBUS编程

1. 通用代码介绍

(1)连接代码

首先,客户端必须要连接上 Dbus,一般来说,系统中会有一个 System Bus 和一个 Session Bus。其次,你需要在 Dbus 中注册一个名字,用于标识自己。为了简单起见,这里先不考虑重名的情况:

DBusError err;
DBusConnection* conn;
int ret;
// initialise the errors
dbus_error_init(&err);
 
// connect to the bus
conn = 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 (NULL == conn) {
    exit(1);
}
// request a name on the bus
ret = dbus_bus_request_name(conn, "test.method.server",
                            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 (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
    exit(1);
}

一般来说,连接上 Dbus 和注册一个名称,应该是在程序最开始运行的时候就会进行的操作。

当然,在程序结束的时候,需要关闭掉与 Dbus 的连接。使用下面的函数:

dbus_connection_close(conn);

(2) 发送信号(Sending Signal)

信号是一种广播的消息,你可以简单的发出一个信号,这样,所有连接在 DBus 总线上并注册了接受对应信号的进程,都会收到这个信号。

为了发出一个信号,需要的只是创建一个 DBusMessage 对象来代表信号,然后追加上一些需要发出的参数,就可以发向总线了。

发完之后还需要释放掉 Message。如果内存不足的话,这下面不少函数都会返回 false,所以一般情况下你都需要处理这些情况的返回值。

dbus_uint32_t serial = 0; // unique number to associate replies with requests
DBusMessage* msg;
DBusMessageIter args;
 
// create a signal and check for errors
msg = dbus_message_new_signal("/test/signal/Object", // object name of the signal
                              "test.signal.Type", // interface name of the signal
                              "Test"); // name of the signal
if (NULL == msg)
{
    fprintf(stderr, "Message Null\n");
    exit(1);
}
 
// append arguments onto signal
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &sigvalue)) {
    fprintf(stderr, "Out Of Memory!\n");
    exit(1);
}
 
// send the message and flush the connection
if (!dbus_connection_send(conn, msg, &serial)) {
    fprintf(stderr, "Out Of Memory!\n");
    exit(1);
}
dbus_connection_flush(conn);
 
// free the message
dbus_message_unref(msg);

(3) 调用方法(Call a Methond)

调用一个远程方法(remote method)与发送一个信号(sending a signal)是很类似的。需要创建一个 DBusMessage,然后通过注册在 DBus 上的名称指定发送的对象,追加相应的参数,但调用方法分为两种,一种是阻塞式的,另一种则可以异步调用。异步调用的时候会得到一个 DBusMessage* 的返回,从这个 DBusMessage 中可以获取一些返回的参数。

调用方法1
DBusMessage* msg;
DBusMessageIter args;
DBusPendingCall* pending;
 
msg = dbus_message_new_method_call("test.method.server", // target for the method call
                                   "/test/method/Object", // object to call on
                                   "test.method.Type", // interface to call on
                                   "Method"); // method name
if (NULL == msg) {
    fprintf(stderr, "Message Null\n");
    exit(1);
}
 
// append arguments
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &param)) {
    fprintf(stderr, "Out Of Memory!\n");
    exit(1);
}
 
// send message and get a handle for a reply
if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) { // -1 is default timeout
    fprintf(stderr, "Out Of Memory!\n");
    exit(1);
}
if (NULL == pending) {
    fprintf(stderr, "Pending Call Null\n");
    exit(1);
}
dbus_connection_flush(conn);
 
// free message
dbus_message_unref(msg);
调用方法2
bool stat;
dbus_uint32_t level;
 
// block until we receive a reply
dbus_pending_call_block(pending);
 
// get the reply message
msg = dbus_pending_call_steal_reply(pending);
if (NULL == msg) {
    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, &args))
    fprintf(stderr, "Message has no arguments!\n");
else if (DBUS_TYPE_BOOLEAN != dbus_message_iter_get_arg_type(&args))
    fprintf(stderr, "Argument is not boolean!\n");
else
    dbus_message_iter_get_basic(&args, &stat);
 
if (!dbus_message_iter_next(&args))
    fprintf(stderr, "Message has too few arguments!\n");
else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
    fprintf(stderr, "Argument is not int!\n");
else
    dbus_message_iter_get_basic(&args, &level);
 
printf("Got Reply: %d, %d\n", stat, level);
 
// free reply and close connection
dbus_message_unref(msg);

(4) 接收消息(Receiving a Signal)

接下来的两种操作是从总线读取消息并处理这些消息。

要接收一个消息,你首先需要告诉 DBus 你对什么样的消息感兴趣

// add a rule for which messages we want to see
dbus_bus_add_match(conn,
                   "type='signal',interface='test.signal.Type'",
                   &err); // see signals from the given interface
dbus_connection_flush(conn);
if (dbus_error_is_set(&err)) {
    fprintf(stderr, "Match Error (%s)\n", err.message);
    exit(1);
}

然后,进程就可以在一个循环中等待这类消息的发生了

/ loop listening for signals being emmitted
while (true) {
 
    // non blocking read of the next available message
    dbus_connection_read_write(conn, 0);
    msg = dbus_connection_pop_message(conn);
 
    // loop again if we haven't read a message
    if (NULL == msg) {
        sleep(1);
        continue;
    }
 
    // check if the message is a signal from the correct interface and with the correct name
    if (dbus_message_is_signal(msg, "test.signal.Type", "Test")) {
        // read the parameters
        if (!dbus_message_iter_init(msg, &args))
            fprintf(stderr, "Message has no arguments!\n");
        else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
            fprintf(stderr, "Argument is not string!\n");
        else {
            dbus_message_iter_get_basic(&args, &sigvalue);
            printf("Got Signal with value %s\n", sigvalue);
        }
    }
 
    // free the message
    dbus_message_unref(msg);
}

(5) 提供被远程调用的方法(Exposing a Method to be called)

在(3) 调用方法(Call a Methond)中,我们看到了调用一个远程方法,这节就是告诉我们怎么样提供一个方法让别的应用程序调用。用下面的程序,就可以把方法关联在那些提供给外部的方法上,并解析出相应的参数,最后构建一个消息返回给调用方法的应用程序。

远程调用的方法1
// loop, testing for new messages
while (true) {
    // non blocking read of the next available message
    dbus_connection_read_write(conn, 0);
    msg = dbus_connection_pop_message(conn);
  
    // loop again if we haven't got a message
    if (NULL == msg) {
        sleep(1);
        continue;
    }
 
    // check this is a method call for the right interface and method
    if (dbus_message_is_method_call(msg, "test.method.Type", "Method"))
        reply_to_method_call(msg, conn);
 
    // free the message
    dbus_message_unref(msg);
}
远程调用方法2:
void reply_to_method_call(DBusMessage* msg, DBusConnection* conn)
{
    DBusMessage* reply;
    DBusMessageIter args;
    DBusConnection* conn;
    bool stat = true;
    dbus_uint32_t level = 21614;
    dbus_uint32_t serial = 0;
    char* param = "";
 
    // read the arguments
    if (!dbus_message_iter_init(msg, &args))
        fprintf(stderr, "Message has no arguments!\n");
    else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
        fprintf(stderr, "Argument is not string!\n");
    else
        dbus_message_iter_get_basic(&args, &param);
    printf("Method called with %s\n", param);
 
    // create a reply from the message
    reply = dbus_message_new_method_return(msg);
 
    // add the arguments to the reply
    dbus_message_iter_init_append(reply, &args);
    if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &stat)) {
        fprintf(stderr, "Out Of Memory!\n");
        exit(1);
    }
    if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &level)) {
        fprintf(stderr, "Out Of Memory!\n");
        exit(1);
    }
 
    // send the reply && flush the connection
    if (!dbus_connection_send(conn, reply, &serial)) {
        fprintf(stderr, "Out Of Memory!\n");
        exit(1);
    }
    dbus_connection_flush(conn);
 
    // free the reply
    dbus_message_unref(reply);
}

2. 实例代码

(1) 客户端代码

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dbus/dbus.h>


DBusConnection* init_bus()
{
	DBusConnection *connection;
	DBusError err;
	int ret;

	dbus_error_init(&err);
        //连接到dbus,建立一个连接,称为对象
	connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
	if(dbus_error_is_set(&err))
	{
		printf("connection error: :%s -- %s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}
        //为这个对象分配一个名字
	ret = dbus_bus_request_name(connection, "hello.world.client", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
	if(dbus_error_is_set(&err))
	{
		printf("Name error: %s -- %s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}
	if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
		return NULL;

	return connection;
}


void send_signal(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	char *str = "hello world!";


	//创建一个signal对象
	//param1: path (这个逻辑来说,可以是任何字符串,只要符合规则即可)
	//param2: interface (一样)
	//param3: 信号方法名(必须与服务端名匹配)
	if((msg = dbus_message_new_signal("/hello", "aa.bb.cc", "alarm_test")) == NULL)
	{
		printf("message is NULL\n");
		return;
	}
#if 0
	 //这个看需求添加,一般来说,信号是一种单向广播,加上这一句变单向单播
	 //param2: bus_name
	if(!dbus_message_set_destination(msg, "hello.world.service"))
        {
                printf("memory error\n");
        }
#endif

	//添加参数的一些接口
	dbus_message_iter_init_append(msg, &arg);
	dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &str);
	//入队
	dbus_connection_send(connection, msg, NULL);
	//发送
	dbus_connection_flush(connection);
	//释放内存
	dbus_message_unref(msg);

	return;
}

void send_method_call(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	DBusPendingCall *pending;
	int a = 100;
	int b = 99;
	int sum;

	msg = dbus_message_new_method_call("hello.world.service", "/hello/world","hello.world", "add");
	if(msg == NULL)
	{
		printf("no memory\n");
		return;
	}

	dbus_message_iter_init_append(msg, &arg);
    	if(!dbus_message_iter_append_basic (&arg, DBUS_TYPE_INT32,&a)){
        	printf("no memory!");
        	dbus_message_unref(msg);
        	return;
    	}
   	if(!dbus_message_iter_append_basic (&arg, DBUS_TYPE_INT32,&b)){
        	printf("no memory!");
        	dbus_message_unref(msg);
        	return;
    	}

    //入队message,等待回复
    //param1: 连接描述符
    //param2: message
    //param3: 相当于一个回调的一个描述符,为了获了返回的消息
    //param4: 超时间. -1代表无限
    if(!dbus_connection_send_with_reply (connection, msg, &pending, -1)){
        printf("no memeory!");
        dbus_message_unref(msg);
        return;
    }

    if(pending == NULL){
        printf("Pending is NULL, may be disconnect...\n");
        dbus_message_unref(msg);
        return;
    }
    //send
    dbus_connection_flush(connection);
    dbus_message_unref(msg);
	
	//阻塞,直到接收到一个响应.
    dbus_pending_call_block (pending);
    msg = dbus_pending_call_steal_reply (pending);
    if (msg == NULL) {
    	printf("reply is null. error\n");
    	return;
    }
    //释放pending内存 
    dbus_pending_call_unref(pending);
    //解析参数
    if (!dbus_message_iter_init(msg, &arg))
        printf("no argument, error\n");
    if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INT32)
    {
    	printf("paramter type error\n");
    }

    dbus_message_iter_get_basic(&arg, &sum);

    printf(" a(%d) + b(%d) = %d\n",a, b, sum);
    dbus_message_unref(msg);
	
    return;
}


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

	connection = init_bus();
	if(connection == NULL)
	{
		printf("connect to bus failed...\n");
		return -1;
	}
        //发送一个信号及一个方法调用
	send_signal(connection);
	send_method_call(connection);
	
	return 0;
}

(2) 服务端代码

service.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <unistd.h>

DBusConnection* init_bus()
{
	DBusConnection *connection;
	DBusError err;
	int ret = 0;

	dbus_error_init(&err);

	//与session dbus 建立连接
	//param1:bus type = {DBUS_BUS_SESSION, DBUS_BUS_SYSTEM} 一个系统dbus, 一个普通用户dbus
	//param2:错误信息,包括错误名与错误信息.
	connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
	if(dbus_error_is_set(&err))
	{
		printf("Connection Error: %s--%s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}

	//为连接设置一个bus name: bus_name;
	//param 1: 连接描述符
	//param 2: 请求bus要分配的bus name(逻辑上讲,bus name可以是任何字符串,只要符合命名规则)
	//param 3: flags ={DBUS_NAME_FLAG_REPLACE_EXISTING, 
	//					DBUS_NAME_FLAG_ALLOW_REPLACEMENT,
	//					DBUS_NAME_FLAG_DO_NOT_QUEUE
	//					 }
	//param 4: err info
	ret = dbus_bus_request_name(connection, "hello.world.service", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
	if(dbus_error_is_set(&err))
	{ printf("Name Error: %s--%s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}

	if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
		return NULL;

	//注册感兴趣的signal: 来自接口dbus.test.signal.sender
	//param1: 连接描述符
	//param2: match rule (常用的类型: sender=
	//									interface=
	//									type=
	//									member= )
	//param3: err info
	//只设置一个type = signal,表示所有信号都接受.也可以加上接口,发送者bus_name
	dbus_bus_add_match(connection, "type='signal'", &err);
	//阻塞,直到消息发送成功.
	dbus_connection_flush(connection);
	if(dbus_error_is_set(&err))
	{
		printf("add Match Error %s--%s\n", err.name, err.message);
		dbus_error_free(&err);
		return connection;
	}
	return connection;
}

void handle_message(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	char *str;

	while(1)
	{
		//param1: 连接描述符
		//param2: 超时时间, -1无限超时时间
		dbus_connection_read_write(connection, 0);
		//从队列中取出一条消息
		msg = dbus_connection_pop_message(connection); 
		if(msg == NULL)
		{
			sleep(1);
			continue;
		}
		//这里应该过滤path,暂且不做
		//打印出消息对象路径
		printf("path: %s\n", dbus_message_get_path (msg));
		//param1: message
		//param2: interface 这个名字必须与发送那个接口一样.才能处理
		//param3: singal name 方法名也必须一样.
		if(dbus_message_is_signal(msg, "aa.bb.cc", "alarm_test"))
		{
			//解析message 参数,0为无参数.
			if(!dbus_message_iter_init(msg, &arg))
			{
				printf("no argument\n");
			}
			//获取第一个参数类型
			if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INVALID)
			{
				//获取参数的值
				dbus_message_iter_get_basic(&arg,&str);
				printf("recv param --: %s\n", str);
			}
			
		}
		else if(dbus_message_is_method_call(msg, "hello.world", "add"))
		{//处理 add 远程调用.
			DBusMessage *rp;
			DBusMessageIter r_arg;
			int a = 0;
			int b = 0;
			int sum = 0;
			printf("service: add  function\n");

			if(!dbus_message_iter_init(msg, &arg))
			{
				printf("no argument!\n");
				goto out;
			}
			if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INT32)
			{
				printf("argument error\n");
				goto out;
			}
			dbus_message_iter_get_basic(&arg, &a);

			if(!dbus_message_iter_next(&arg))
			{
				printf("too few argument!\n");
				goto out;
			}
			//check argument type....
			dbus_message_iter_get_basic(&arg, &b);
			sum = a + b;
out:
			//new 一个回应对象
			rp = dbus_message_new_method_return(msg);
			dbus_message_iter_init_append(rp, &r_arg);
			if(!dbus_message_iter_append_basic(&r_arg, DBUS_TYPE_INT32, &sum))
			{
				printf("no memory!!\n");
				return; 
			}

			//param3: 这个跟消息序列有关
			if(!dbus_connection_send(connection, rp, NULL))
			{
				printf("no memory!!\n");
				return;
			}
			dbus_connection_flush(connection);
			dbus_message_unref(rp);
		}
		//释放空间
		dbus_message_unref(msg);
	}
	//dbus_bus_remove_match();
	
}

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

	connection = init_bus();
	if(connection == NULL)
	{
		printf("connect the dbus failed...\n");
		return -1;
	}

	handle_message(connection);

	return 0;
}

(3)执行结果

客户端

在这里插入图片描述

服务端

在这里插入图片描述

参考博客:
D-Bus 学习
DBUS基础知识(非常全面)
DBus 入门与应用 -- DBus 的 C 编程接口
dbus 简单编程(二)

Logo

更多推荐