DBUS入门与C编程
Posted Jocelin47
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DBUS入门与C编程相关的知识,希望对你有一定的参考价值。
一、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, ¶m))
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, ¶m);
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以上是关于DBUS入门与C编程的主要内容,如果未能解决你的问题,请参考以下文章