如果你想在Linux系统下开发软件,且需要用到进程间通信时,我强烈建议你了解和使用Dbus。Dbus是实质上一个适用于桌面应用的进程间的通讯机制,即所谓的IPC机制。Dbus是一个进程间的通信机制,可以是应用与应用之间的通信,也可以是应用与系统之间的通信,其提供了一个低时延、低消耗的IPC通讯,因为它采用了二进制的数据交换协议,不需要转换成文本化的数据进行交换,DBUS提供了面向多重对象系统的包装,可以在原有的面向对象的应用框架下使用DBUS,不需要学习新的概念和规范等。DBUS_BUS_SYSTEM(system Dbus)用于系统通讯、监控系统更新事件,DBUS_BUS_SESSION (session Dbus)用于多个桌面应用之间相互通讯。
Dbus-glib是GNU标准库,在Dbus接口上封装,方便上层服务与应用更好的使用。其形如一个Dbus代理服务器,由它进行所有Dbus消息的遍历与转发,服务端与消息发送端只需要向dbus deamon申请注册唯一的dbus name 、绑定GOBJECT后,dbus deamon就会将申请连到到该dbus name的Dbus信息转发给指定应用。dbus daemon不对消息重新排序,如果发送了两条消息到同一个进程,它们将按照发送顺序接受到。接受进程并需要按照顺序发出应答消息,例如在多线程中处理这些消息,应答消息的发出是没有顺序的。消息都有一个序列号可以与应答消息进行配对。
glib绑定接口在libdbus-glib-1-dev中定义,详情见:/usr/include/dbus-1.0/dbus/dbus-glib.h,dbus的接口描述文件统一采用utf-8编码。
表一 dbus和glib的数据类型映射表
D-Bus type | Description | GType | C typedef | Free function | Notes |
---|---|---|---|---|---|
a (ARRAY) | 数组 | 数组元素的类型由a后面的标记决定。如:”as”是字符串数组,具体见表二表三 | |||
b (BOOLEAN) | 布尔值 | G_TYPE_BOOLEAN | |||
d (DOUBLE) | IEEE 754双精度浮点数 | G_TYPE_DOUBLE | |||
g (SIGNATURE) | 类型签名 | ||||
i (INT32) | 32位有符号整数 | G_TYPE_INT | Will be changed to a G_TYPE_INT32 once GLib has it | ||
n (INT16) | 16位有符号整数 | G_TYPE_INT | Will be changed to a G_TYPE_INT16 once GLib has it | ||
o (OBJECT_PATH ) | 对象路径 | DBUS_TYPE_G_PROXY | g_object_unref | The returned proxy does not have an interface set; use dbus_g_proxy_set_interface to invoke methods | |
q (UINT16) | 16位无符号整数 | G_TYPE_UINT | Will be changed to a G_TYPE_UINT16 once GLib has it | ||
s (STRING) | 零结尾的UTF-8字符串 | G_TYPE_STRING | char * | g_free | |
t (UINT64) | 64位无符号整数 | G_TYPE_GUINT64 | |||
u (UINT32) | 32位无符号整数 | G_TYPE_UINT | Will be changed to a G_TYPE_UINT32 once GLib has it | ||
v (VARIANT) | 容器 | G_TYPE_VALUE | GValue * | g_value_unset | 可放任意数据类型的容器,数据中包含类型信息。如glib中的GValue |
x (INT64) | 64位有符号整数 | G_TYPE_GINT64 | |||
y (BYTE) | 8位无符号整数 | G_TYPE_UCHAR | |||
t (UINT64) | 64位无符号整数 |
() 定义结构时使用。例如”(i(ii))”
{} 定义键-值对时使用。例如”a{us}”
数组”a(i(ii))”的元素是一个结构。用括号将成员的类型括起来就表示结构了,结构可以嵌套。
数组”a{sv}”的元素是一个键-值对。”{sv}”表示键类型是字符串,值类型是VARIANT。
dbus数据也有包容器类型,像DBUS_TYPE_ARRAY 和 DBUS_TYPE_STRUCT,dbus的数据类型可以是嵌套的,如有一个数组,内容是字符串的数组集合。但并不是所有的类型都有普通的使用,DBUS_TYPE_STRUCT应该可以包容非基本类型的数据类型。glib绑定尝试使用比较明显的方式进行声明。
表二 数组表
D-Bus type signature | Description | GType | C typedef | Free function |
---|---|---|---|---|
ay | Array of bytes | DBUS_TYPE_G_BYTE_ARRAY | GArray * | g_array_free |
au | Array of uint | DBUS_TYPE_G_UINT_ARRAY | GArray * | g_array_free |
ai | Array of int | DBUS_TYPE_G_INT_ARRAY | GArray * | g_array_free |
ax | Array of int64 | DBUS_TYPE_G_INT64_ARRAY | GArray * | g_array_free |
at | Array of uint64 | DBUS_TYPE_G_UINT64_ARRAY | GArray * | g_array_free |
ad | Array of double | DBUS_TYPE_G_DOUBLE_ARRAY | GArray * | g_array_free |
ab | Array of boolean | DBUS_TYPE_G_BOOLEAN_ARRAY | GArray * | g_array_free |
as | Array of strings | G_TYPE_STRV | char ** | g_strfreev |
表三 字典(dict or hash)表
D-Bus type signature | Description | GType | C typedef | Free function |
---|---|---|---|---|
a{ss} | Dictionary mapping strings to strings | DBUS_TYPE_G_STRING_STRING_HASHTABLE | GHashTable * | g_hash_table_destroy |
示例源码
编写session dbus服务端
xml的编写及绑定文件的生成
在GLib中,通过dbus表现出GObject,必须写XML文件描述这个对象的方法等属性。通过XML文件和dbus-binding-tool工具,可以很方便的自动创建出易于使用的dbus代理对象。例如下面的org.freedesktop.myglibdbus.xml文件描述了三个函数和一个信号,其中函数work有一个输入参数msg(char)和一个输出参数ret(gint),函数receive有一个输入参数msg(char)和一个输出参数ret(char*),函数exit有一个输出参数ret(gint),信号info_alert传递一个参数类型为字符串的变量。一个node可以有多个interface ,一个interface可以有多个method(函数)或signal(信号)。 org.freedesktop.myglibdbus.xml的具体内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<?xml version="1.0" encoding="UTF-8" ?>
<node name="/org/freedesktop/myglibdbus">
<interface name="org.freedesktop.myglibdbus">
<method name="work">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<arg type="s" name="msg" direction="in"/>
<arg type="i" name="ret" direction="out" />
</method>
<method name="receive">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<arg type="s" name="msg" direction="in"/>
<arg type="s" name="ret" direction="out" />
</method>
<method name="exit">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<arg type="i" name="ret" direction="out" />
</method>
<signal name="info_alert">
<arg type="s" name="value" direction="out" />
</signal>
</interface>
</node>
在上面的XML中可以看到一行<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
,其实,Annotations的方式有四种,分别如下:1
2
3
4org.freedesktop.DBus.GLib.CSymbol
org.freedesktop.DBus.GLib.Async
org.freedesktop.DBus.GLib.Const
org.freedesktop.DBus.GLib.ReturnVal
通过dbus-binding-tool生成绑定文件
在进行代码编写之前,先确保系统安装了dbus相关的开发包,执行以下命令:
$ sudo apt install libglib2.0-dev libdbus-glib-1-dev libdbus-1-dev
运行dbus-binding-tool工具(该工具来自包libdbus-glib-1-dev),如下所示:
1
2
3$ dbus-binding-tool --mode=glib-server --prefix=myglibdbus org.freedesktop.myglibdbus.xml --output=myglibdbus-glue.h
或者
$ dbus-binding-tool --mode=glib-server --prefix=myglibdbus org.freedesktop.myglibdbus.xml > myglibdbus-glue.h执行上面的命令,则生成了绑定文件myglibdbus-glue.h,该文件无需修改,直接在本地代码中include使用即可。”–prefix”参数定义了对象前缀。设对象前缀是$(prefix),则生成的DBusGObjectInfo结构变量名就是
dbus_glib_$(prefix) _object_info
,如:dbus_glib_myglibdbus_object_info。
绑定文件会为接口方法定义回调函数。回调函数的名称是这样的:首先将xml中的方法名称转换到全部小写,下划线分隔的格式,然后增加前缀$(prefix)_
。例如xml中的函数work,绑定文件就会引用一个名称为$(prefix)_work
的函数,即:myglibdbus_work。
创建对象
dbus-glib定义向dbus daemon申请一个注册信息的形式为GObject(C语言)的对象。dbus-glib用GObject实现dbus对象,故需要实现一个对象,继承于GObject。
新建文件myglidbus.h和myglibdbus.c,在头文件myglidbus.h中定义对象,如下所示:
1
2
3
4
5
6
7typedef struct _MyGlibDbus {
GObject parent;
} MyGlibDbus;
typedef struct _MyGlibDbusClass {
GObjectClass parent_class;
} MyGlibDbusClass;在GObject中,类是两个结构体的组合,一个是实例结构体,另一个是类结构体,MyGlibDbus是实例结构体,MyGlibDbusClass是类结构体。
实现类类型的定义
在myglibdbus.c文件中加入
G_DEFINE_TYPE(MyGlibDbus, myglibdbus, G_TYPE_OBJECT);
G_DEFINE_TYPE可以让GObject库的数据类型系统能够识别我们所定义的MyGlibDbus类类型,它接受三个参数,第一个参数是类名,即MyGlibDbus;第二个参数则是类的成员函数(面向对象术语称之为“方法”或“行为”)名称的前缀,例如myglibdbus_get_type函数即为MyGlibDbus类的一个成员函数,“myglibdbus”
在myglibdbus.c文件中,需要include上面生成的绑定文件,即代码中加入:
#include "myglibdbus-glue.h"
,调用dbus_g_object_class_install_info进行类的初始化,传递对象和对象信息进去,如:dbus_g_object_type_install_info(MYGLIBDBUS_TYPE_OBJECET, &dbus_glib_myglibdbus_object_info);
为了执行XML中的method,这里需要对每个method定义一个C函数,这里拿work函数进行说明,定义的C函数为:
gint myglibdbus_work(MyGlibDbus *dbus, gchar *msg, DBusGMethodInvocation *ret_value, GError **error)
其中,第一个参数必须是对象实例的指针,跟在实例指针后面的参数 msg 是方法的输入参数,ret_value为输出参数,最后一个参数必须为
GError **
,如果函数返回失败,必须使用g_set_error填充该错误参数。最后可以使用dbus_g_connection_register_g_object输出一个对象,如:
dbus_g_connection_register_g_object(connection, MYGLIBDBUS_PATH, G_OBJECT(dbus));
声明类的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15GType myglibdbus_get_type(void);//向GObject库所提供的类型管理系统提供要注册的MyGlibDbus类类型的相关信息,可以不实现,但必须要声明
#define MYGLIBDBUS_TYPE_OBJECET (myglibdbus_get_type())
//声明类的函数(类成员的构造函数)
void myglibdbus_init(MyGlibDbus *dbus)
{
}
//声明类的函数(类结构的构造函数,与类成员构造函数区别在于,该构造函数只在该类定义时运行一次,常用来进行消息信号的初始化等。而myglibdbus_init则在创建成员时都会调用一次(如obj = g_object_new))
void myglibdbus_class_init(MyGlibDbusClass * kclass)
{
}向dbus deamon申请注册
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
49DBusGConnection * connection = NULL;
DBusConnection *connect;
DBusGProxy * dbus_proxy = NULL;
GError * error = NULL;
guint request_name_result;
gint ret;
dbus_g_thread_init();
dbus = (MyGlibDbus*)myglibdbus_new();
loop = g_main_loop_new(NULL, FALSE);
connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
if(connection == NULL){
g_error("%s", error->message);
goto out;
}
//申请一个会话总线 DBUS_SERVICE_DBUS: org.freedesktop.DBus DBUS_PATH_DBUS: /org/freedesktop/DBus DBUS_INTERFACE_DBUS:org.freedesktop.DBus
dbus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
//调用dbusdaemon的函数“RequestName”,申请一个DBUS名为org.freedesktop.myglibdbus的注册信息
ret = dbus_g_proxy_call(dbus_proxy, "RequestName", &error,
G_TYPE_STRING, MYGLIBDBUS_NAME,
G_TYPE_UINT,
DBUS_NAME_FLAG_DO_NOT_QUEUE,
G_TYPE_INVALID,
G_TYPE_UINT, &request_name_result,
G_TYPE_INVALID);
if(!ret){
g_error("RequestName failed:%s", error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
g_object_unref(G_OBJECT(dbus_proxy));
//already running
if(request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
exit(EXIT_FAILURE);
dbus_g_object_type_install_info(MYGLIBDBUS_TYPE_OBJECET, &dbus_glib_myglibdbus_object_info);//向dbus-glib登记对象信息
//申请之前定义的一个对象MyGlibDbus,将该对象与bus绑定
dbus_g_connection_register_g_object(connection, MYGLIBDBUS_PATH, G_OBJECT(dbus));
g_main_loop_run(loop);//申请创建一个主循环
out:
g_main_loop_unref(loop);
g_object_unref(dbus);
通过Dbus-glib编写客户端
使用dbus_g_proxy_call调用dbus服务的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16GError *err = NULL;
int ret = 0;
if (!dbus_g_proxy_call(remote_object, "work", &err,
G_TYPE_STRING, "Hello world!", G_TYPE_INVALID,
G_TYPE_INT, &ret, G_TYPE_INVALID)) {
}
else {
if (err != NULL) {
if(err->domain == DBUS_GERROR && err->code == DBUS_GERROR_REMOTE_EXCEPTION)
printf("dbus send exception %s:%s",dbus_g_error_get_name(err), err->message);
else
printf("dbus send Error : %s\n", err->message);
g_clear_error(&err);
}
}使用dbus-binding-tool –mode=glib-client方式调用dbus服务的方法
$ dbus-binding-tool --mode=glib-client --prefix=myglibdbus org.freedesktop.myglibdbus.xml > myglibdbus_proxy.h
打开myglibdbus_proxy.h看到服务端提供的work方法已经转为:
1
2
3
4
5
6static inline gboolean
org_freedesktop_myglibdbus_work (DBusGProxy *proxy, const char * IN_msg, gint* OUT_ret, GError **error)
{
return dbus_g_proxy_call (proxy, "work", error, G_TYPE_STRING, IN_msg, G_TYPE_INVALID, G_TYPE_INT, OUT_ret, G_TYPE_INVALID);
}客户端可以直接调用org_freedesktop_myglibdbus_work,而不再需要使用上面的dbus_g_proxy_call来调用work函数。
示例代码工程创建和编译演示
编写org.freedesktop.myglibdbus.xml文件
新建文件myglidbus.h、myglibdbus.c、server.c和client.c,并完成代码编写,其中myglidbus.h、myglibdbus.c、server.c三个文件是服务端代码文件,client.c是客户端代码文件。
执行autoscan,生成 configure.scan 和 autoscan.log,将configure.scan 修改为 configure.ac, 并修改configure.ac的内容。
$ autoscan
configure.ac的初始内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/server.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stdlib.h string.h])
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT执行aclocal,生成aclocal.m4(处理本地宏文件)和 autom4te.cache
$ aclocal
执行autoheader,生成生成config.h.in
$ autoheader
执行autoconf,根据 configure.ac 和 aclocal.m4 生成 configure
$ autoconf
编写 Makefile.am 和 src/Makefile.am
增加 NEWS、README、AUTHORS、ChangeLog
执行automake,生成 Makefile.in,src/Makefile.in, compile, COPYING, depcomp, INSTALL, install-sh 和 missing (根据 Makefile.am 和 aclocal.m4)
$ automake --add-missing
执行configure,生成 Makefile, src/Makefile, config.log 和 config.status
$ ./configure
执行make,生成二进制执行文件(src下server和client)
$ make
未来可以改进的地方
多线程防冲突
上面示例代码只是单线程实现dbus调用。如果多线程的情况下,以及库函数情况下,为确保不同线程使用不同的DBusConnection,在创建dbus总线时要注意使用关键字创建各自私有的总线。
客户端实现
1
2
3
4
5
6
7
8
9
10
11
12GMainContext*main_context = NULL;
main_context =g_main_context_new();//申请独立的context
/*
is dbus_g_bus_get_private, not dbus_g_bus_get
dbus_g_bus_get_private申请的私有总线连接在使用完成后,要使用dbus_g_connection_close先关闭连接后再释放资源dbus_g_connection_unref,
否则只调用dbus_g_connection_unref会报“私有连接无法关闭”,导致内存泄露。
*/
bus = dbus_g_bus_get_private(DBUS_BUS_SESSION,main_context, &error);
…
dbus_g_connection_close(bus);//私有的总线连接要先close才能unref
dbus_g_connection_unref(bus);
g_main_context_unref(main_context);//g_main_context_unref()要与g_main_context_new() 配合使用,如果申请的资源未释放,会导致文件句柄泄露消息接收端实现:
1
2
3
4
5GMainContext*main_context = NULL;
main_context= g_main_context_new();//申请独立的context
mainloop= g_main_loop_new (main_context, FALSE);
g_main_loop_unref(mainloop);
g_main_context_unref(main_context);以这种方式实现的消息接收端,在多进程接收相同消息的情况下,只有一个进程能够接收到该消息,如果要多进程都能接收到消息,实现如下(使用系统默认的context与总线绑定):
1
2main_context = g_main_context_default();
mainloop = g_main_loop_new (main_context,FALSE);或
1
mainloop = g_main_loop_new (NULL, FALSE);
dbus_g_connection_close的实现如下:
1
2
3
4
5
6#define _DBUS_POINTER_UNSHIFT(p) ((void*) (((char*)p)- sizeof (void*)))
#define DBUS_CONNECTION_FROM_G_CONNECTION(x) ((DBusConnection*)_DBUS_POINTER_UNSHIFT(x))
void dbus_g_connection_close(DBusConnection * connection)
{
returndbus_connection_close(DBUS_CONNECTION_FROM_G_CONNECTION(connection));
}