Dbus-glib

如果你想在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
4
org.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
    7
    typedef 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
    15
    GType 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
    49
    DBusGConnection * 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
    16
    GError *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
    6
    static 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
    12
    GMainContext*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
    5
    GMainContext*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
    2
    main_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));
    }
李翔 wechat
微信交流
谢谢支持
0%