DBUS
什么是DBUS
DBUS是一种进程间通信(IPC)机制,支持进程间一对一、多对多通信。系统启动时,会同时启动一个后台服务进程(dbus-daemon),当两个进程间进行消息传递时,进程A会先将消息传递给dbus-daemon,然后dbus-daemon在将消息转发给目标进程。
DBUS的种类
最低级的dbus库,非libdbus莫属了。用起来相对来说会比较麻烦。同时,还有一些基于特定框架(Qt, Glib、Python、C# 、Ruby、Java等等)的dbus编程框架的实现,它们有的依赖于libdbus,有的是基于Dbus Spec进行重新实现。
DBUS的应用
在dbus的官网上,可以找到使用dbus的项目。像Android中使用的Wi-Fi管理配置工具wpa_supplicant、Linux中的蓝牙协议栈BlueZ均使用到了dbus通信框架。
几个DBUS中的基本概念
以下内容摘自《D-Bus Tutorial》,对于初识dbus的同学,建议首先阅读此文章。
Object和ObjectPath
熟悉OO的同学可能更了解Object的概念。在Java、CPP中,Object是作为类的一个实例出现的。简单讲,一条狗,一个人都可以称之为一个对象。对象有自己的状态和行为。例如:
Object | 人 |
---|---|
状态 | Name:二狗 Sex: 男 High: 一米二 |
行为 | 吃饭、睡觉、打豆豆 |
这些对象和行为放在编程语言内称之为Method和Property。
在libdbus中,有个概念NativeObject,但是并不理会这些NativeObject,而是使用了一个ObjectPath的概念。高层应用编程中创建对象进行通信时候,都会指定一个ObjectPath,用于dbus低层进行消息的路由。
Object Path的命名
/org/bluez/dev_00_11_22_33_44_55
/com/company/department/kafka
这个和Java中的包(Package)的概念很相似。
Method和Signal
方法就是一个函数,具有输入输出参数,信号是用来广播的。如果一个对象需要获取这个信号,它需要先对这个信号进行监听(watch)。信号中也可以包含有效信息。
在 D-BUS 中有四种类型的消息:
方法调用(method calls)
方法返回(method returns)
信号(signals)
错误(errors)。
要执行 D-BUS 对象的方法,您需要向对象发送一个方法调用消息。它将完成一些处理(就是执行了对象中的Method,Method是可以带有输入参数的。)并返回,返回消息或者错误消息。信号的不同之处在于它们不返回任何内容:既没有“信号返回”消息,也没有任何类型的错误消息。
Interface
接口是一类方法和信号的组合。每个对象可以包含一个和多个接口。接口的命名方法:com.module.bluetooth.scan
、org.freedesktop.Introspectable
。
Proxy
在调用远程对象的方法时,可以在本地创建一个代理对象,这样就可以向调用本地对象一样,调用远程对象了。
BusName
当一个应用程序连接到总线上,总线会为他分配一个唯一的总线名称(unique connect name),这个名称以:开头。
同时,应用程序也会给他指定一个名字(well-known name)。例如"com.alkaid.TextEditor"
总线名称还会有另外一个用途,当一个应用的生命周期结束,或者崩溃。Kernel会发送notification给其他的应用,通知某个应用生命周期已经结束。
Address
地址就是server用于监听,client用于连接的地方,对于bus-daemon来说,它就是server,应用就是client。
对于libdbus,通过检查环境变量获取session dbus-daemon的地址;通过检查一个特定的unix socket domain,获得system dbus-daemon的地址。
如果使用dbus,但是不使用dbus-daemon,那么你就要自己定义server和client,而且需要定义一套识别server和client的方法。这个并不常用。
- 当Client连接到bus-daemon上时,他也就有了一个bus-name,应用为它指定一个well-known名称,bus-daemon为它分配一个unique名称。
- 消息传递的时候,会根据ObjectPath,送至相应的对象实例。
Method Call的使用场景
一个Method消息从进程A发送到进程B,B进程将会返回一个Method消息或者Error消息,A进程的消息中包含一个唯一的序列号,同样B进程返回的消息中也包含一个同样的序列号。
如果使用proxy的方式,通过调用本地proxy对象的方法,proxy将构建一个对象发送到远端,触发远端对象的方法。
Signal的应用场景
Signal是个广播消息,是不需要响应的。
bus-daemon只会把signal转发给需要的对象。所以接收方需要在bus-daemon注册match-rules,包含发送方名称和signal名称,然后就会接收到相应的信号了。
signal的处理流程如下:
- 一个signal发送到bus-daemon,signal包含bus-name、signal name、intf name、以及payload。
- 接收方需要注册match-rules,match-rules用来告诉bus-daemon那些是我感兴趣的signal,然后bus-daemon进行转发。
- 对于接收方,如果使用libdbus,他会检查bus-name和signal,然后决定如何处理;如果使用binding,binding将会在proxy object上发出一个native signal。
Introspection
dbus提供了一个方法org.freedesktop.DBus.Introspectable.Introspect
,调用这个方法将会返回一个XML的字符串,可以用来查询特定对象支持的方法名。但是,查询出来的方法只是对象理论上支持的方法,实际并未实现。
GDbus编程示例
dbus的框架有很多,最近在学习蓝牙协议栈blueZ,使用到了GDbus,这个示例也是用此框架,初次使用,需要安装相应的库,可以参考此篇文章
如下Case,调用了org.freedesktop.DBus.ListActivatableNames
方法,返回dbus-daemon上所有可以启动的服务名称。
#include
#include
#include
#include
#include
#include
#include
#define LOG(fmt, args...) printf(fmt, ##args);
int main(void) {
DBusError err;
DBusConnection *dbus_conn;
DBusMessage *message;
int ret = 0;
int i = 0;
DBusMessageIter Iter, subIter;
char *arr;
DBusPendingCall *pending;
dbus_error_init(&err);
dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if(dbus_error_is_set(&err)) {
LOG(err.message);
return -1;
}
if(NULL == dbus_conn) {
LOG("-2");
return -2;
}
/*
//you'd create .conf in /etc/dbus-1/system.d/*.conf
ret = dbus_bus_request_name(dbus_conn, "test.dbus.methodcall", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(dbus_error_is_set(&err)) {
LOG("%s\n", err.message);
return -3;
}
if(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
LOG("-4\n");
return -4;
}
*/
//此方法没有输入参数,所以我们无需添加输入参数
message = dbus_message_new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListActivatableNames");
dbus_connection_send_with_reply(dbus_conn, message, &pending, -1);
dbus_connection_flush(dbus_conn);
dbus_message_unref(message);
//此处阻塞,知道调用完成,如果上面设定了timeout,超时也会返回
dbus_pending_call_block(pending);
//get reply message
message = dbus_pending_call_steal_reply(pending);
if(!dbus_message_iter_init(message, &Iter)) {
LOG("-6\n");
}
LOG("type '%c'\n", dbus_message_iter_get_arg_type(&Iter));
dbus_message_iter_recurse(&Iter, &subIter); //因为方法返回的是array String,这里需要吧Iter中的数据迭代到subIter中,然后再get_basic
LOG("type '%c'\n", dbus_message_iter_get_arg_type(&subIter));
for(i = 0; i < 49; i++) {
dbus_message_iter_get_basic(&subIter , &arr);
dbus_message_iter_next(&subIter); // 指向下一个
LOG("%s\n", arr);
}
printf("hello World\n");
}