DBus for IPC in Qt

By Kong-dao Xing

Linux 系统IPC种类:

  • 信号
  • 管道
  • 命名管道
  • 信号量
  • 消息队列
  • 共享内存
  • 内存映射文件
  • 套接字

DBus 概念

总线

持久化的系统总线(system bus)
  • 系统开机引导时就启动, system bus 由操作系统和后台进程使用, 安全性好。
会话总线(session bus)
  • 用户登录时启动

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

DBus 库

1. 函数库libdbus,用于两个应用程序互相联系和交互消息。
2. 一个基于libdbus构造的消息总线守护进程(dbus-daemon),可同时与多个应用程序相连,
      并能把来自一个应用程序的消息路由到0或者多个其他程序。
3. 基于特定应用程序框架的封装库或捆绑(wrapper libraries or bindings )。
      例如,libdbus-glib和libdbus-qt,还有绑定在其他语言,
      例如Python的。大多数开发者都是使用这些封装库的API,
      因为它们简化了D-Bus编程细节。libdbus被有意设计成为更高层次绑定的底层后端(low-levelbackend )。
      大部分libdbus的 API仅仅是为了用来实现绑定。

优点

低延迟:DBus一开始就是用来设计成避免来回传递和允许异步操作的。因此虽然在Application和Daemon之间是通过socket实现的,但是又去掉了socket的循环等待,保证了操作的实时高效。

低开销:DBus使用一个二进制的协议,不需要转化成像XML这样的文本格式。因为DBus是主要用来机器内部的IPC,而不是为了网络上的IPC机制而准备的.所以它才能够在本机内部达到最优效果。

高可用性:DBus是基于消息机制而不是字节流机制。它能自动管理一大堆困难的IPC问题。同样的,DBus库被设计来让程序员能够使用他们已经写好的代码。而不会让他们放弃已经写好的代码,被迫通过学习新的IPC机制来根据新的IPC特性重写这些代码。

 

绝大部分的linux桌面环境都使用的dbus作为进程间通信。 比如:GNOME 和KDE

支持golang, c/c++, python 语言

D-Bus进程通信简单框架

D-Bus进程通信简单框架

Create DBus

获取system bus

QDBusConnection systemBus = QDBusConnection::systemBus(); 

获取session bus

QDBusConnection sessionBus = QDBusConnection::sessionBus();

注册服务

bool QDBusConnection::registerService ( const QString & serviceName )

注册对象接口

1
2
3
4
5
6
7
8
9
10
11
bool QDBusConnection::registerObject ( const QString & path, QObject * object,
RegisterOptions options = ExportAdaptors )
//常用Option选项
QDBusConnection::ExportAdaptors
QDBusConnection::ExportNonScriptableSlots
QDBusConnection::ExportNonScriptableSignals
QDBusConnection::ExportNonScriptableProperties
QDBusConnection::ExportNonScriptableInvokables // Q_INVOKABLE
QDBusConnection::ExportAllContents =
QDBusConnection::ExportNonScriptableSlots|QDBusConnection::ExportNonScriptableSignals
|QDBusConnection::ExportNonScriptableInvokables|QDBusConnection::ExportScriptableProperties

例子:

Dbus Server

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
// person.h
#include <QObject>
class Person : public QObject {
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.brion.interface")
public:
    explicit Person(QObject *parent = 0);
signals:
    void nameChanged(QString);
    void ageChanged(int);
public slots:
    QString name() const { return m_name; }
    // can't be reference
    void setName(QString name) {
        m_name = name;
    }
    int age() const { return m_age; }
    void setAge(int age) {
        m_age = age;
    }
private:
    QString m_name;
    int m_age;
};
// main.cpp
#include <QtDBus/QDBusConnection>
#include <person.h>
int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QDBusConnection sessionBus = QDBusConnection::sessionBus();
    if (sessionBus.registerService("com.brion.service")) {
        sessionBus.registerObject("/", new Person(),
        QDBusConnection::ExportAllContents);
    }
    return a.exec();
}
d-feet 查看dbus 服务

DBus Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.cpp
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDebug>
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    QDBusInterface interface("com.brion.service", "/", "com.brion.interface");
    interface.call("setName", "Brion");
    QDBusReply<QString> reply = interface.call("name");
    if (reply.isValid()) {
        qDebug()<<"name = "<<reply.value();
    }
    interface.call("setName", "ASML");
    reply = interface.call("name");
    if (reply.isValid()) {
        qDebug()<<"name = "<<reply.value();
    }
    return a.exec();
}

输出:

name =  "Brion"
name =  "ASML"

客户端调用服务端的函数

方式一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 传参数
QDBusMessage msg = QDBusMessage::createMethodCall("com.brion.service",
"/", "com.brion.interface", "setName");
msg << QString("Brion");
QDBusMessage response = QDBusConnection::sessionBus().call(msg);
// 获取返回值
QDBusMessage msg = QDBusMessage::createMethodCall("com.brion.service",
"/", "com.brion.interface", "name");
QDBusMessage response = QDBusConnection::sessionBus().call(msg);
// 判断Method 是否被正确返回
if(response.type() == QDBusMessage::ReplyMessage)
{
    // QDBusMessage的arguments不仅可以用来存储发送的参数,也用来存储返回值
    // 这里取得 name 的返回值
    QString name= response.arguments().takeFirst().toString();
}

方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QDBusInterface interface("com.brion.service", "/",
"com.brion.interface",
QDBusConnection::sessionBus());
if(!interface.isValid())
{
    qDebug() << qPrintable(QDBusConnection::sessionBus().lastError().message());
    exit(1);
}
// 调用 setName,
interface.call("setName", "Brion");
// 调用 name,
QDBusReply<QString> reply = interface.call("name");
if(reply.isValid())
{
    QString value = reply.value();
    qDebug()<<"value = "<<value ;
}

方式三: 异步调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
QDBusPendingCall async = interface->asyncCall("setName", "Brion");
// async.waitForFinished ()
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(callFinishedSlot(QDBusPendingCallWatcher*)));
void MyClass::callFinishedSlot(QDBusPendingCallWatcher *call)
{
    QDBusPendingReply<QString> reply = *call;
    if (!reply.isError()) {
        QString name= reply.argumentAt<0>();
        qDebug()<<"name = "<<name;
    }
    call->deleteLater();
}

客户端接收服务端信号

方式一:

1
2
3
4
QDBusConnection::sessionBus().connect("com.brion.service", "/",
                                                                      "com.brion.interface",
                          "ageChanged", this,
                          SLOT(onAgeChanged(int)));

方式二:

1
2
3
QDBusInterface *interface = new QDBusInterface ("com.brion.service", "/",
                                 "com.brion.interface",DBusConnection::sessionBus());
QObject::connect(&interface, SIGNAL(ageChanged(int)), object, SLOT(onAgeChanged(int)));

QtDBus 默认支持的数据类型

Qt type
D-Bus equivalent type
Qt type
D-Bus equivalent type
uchar BYTE
bool BOOLEAN
short INT16
ushort UINT16
int INT32
uint UINT32
qlonglong INT64
qulonglong UINT64
double DOUBLE
QString STRING
QDBusVariant VARIANT
QDBusObjectPath OBJECT_PATH
QDBusSignature SIGNATURE

同时支持
QStringList, QByteArray

自定义类型

方式一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct MyStructure {
    int count;
    QString name;
};
Q_DECLARE_METATYPE(MyStructure)
// 服务端和客户端都要重载
QDBusArgument &operator<<(QDBusArgument &argument, const MyStructure &mystruct)
{
    argument.beginStructure();
    argument << mystruct.count << mystruct.name;
    argument.endStructure();
    return argument;
}
// Retrieve the MyStructure data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument, MyStructure &mystruct)
{
    argument.beginStructure();
    argument >> mystruct.count >> mystruct.name;
    argument.endStructure();
    return argument;
}
qRegisterMetaType<MyStructure>("MyStructure");
qDBusRegisterMetaType<MyStructure>();

方式二:

利用QByteArray实现自定义类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass {
private:
    int count;
    QString name;
}
QDataStream & operator<< (QDataStream& stream, const MyClass& myclass)
{
    stream<<myclass.count;
    stream<<myclass.name;
}
QDataStream & operator>> (QDataStream& stream, const MyClass& myclass)
{
    stream>>myclass.count;
    stream>>myclass.name;
}

服务端先把数据写入QByteArray。 客户端通过QDataStream把数据从QByteArray读出

编写Adaptor

如果注册对象时,使用QDBusConnection::ExportAllContents, 会导致很多的接口都暴露给用户。 为此, 可以编写Adaptor来控制接口

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
50
51
52
53
54
55
56
// adaptor.h
#include <QDBusAbstractAdaptor>
#include <QDebug>
class Adaptor : public QDBusAbstractAdaptor
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.brion.interface")
    Q_CLASSINFO("D-Bus Introspection", ""
            "<interface name=\"com.brion.interface\">\n"
           " <method name=\"setAge\">\n"
            " <arg type=\"i\" direction=\"in\"/>"
           " </method>\n"
            " <signal name=\"ageChanged\">\n"
            " <arg type=\"i\" direction=\"out\"/>"
            " </signal>"
            "</interface>\n"
            "")
public:
Adaptor::Adaptor(QObject* parent = 0) : QDBusAbstractAdaptor(parent) {
    // 是否转发signals
    setAutoRelaySignals(true);
}
signals:
    void ageChanged(int age);
public slots:
    void setAge(int age) {
        QMetaObject::invokeMethod(parent(), "setAge", Q_ARG(int, age));
    }
};
// person.h
#include <QObject>
class Person : public QObject
{
    Q_OBJECT
public:
    Person::Person(QObject *parent) : QObject(parent) {
        m_age = 0;
        new Adaptor(this);
    }
signals:
    void nameChanged(QString);
    void ageChanged(int);
public slots:
    QString name() const { return m_name; }
    void setName(QString name) {
        m_name = name;
    }
    int age() const { return m_age; }
    void setAge(int age) {
        m_age = age;
        emit ageChanged(m_age);
    }
    private:
        QString m_name;
        int m_age;
};

XML数据类型定义

基本类型

type
code
type
code
BYTE y
BOOLEAN b
INT16 n
UINT16 q
INT32 i
UINT32 u
INT64 x
UINT64 t
DOUBLE d
STRING s
VARIANT v
OBJECT_PATH o

void setAge(int age)

1
2
3
<method name=\"setAge\">
  <arg type=\"i\" direction=\"in\"/> // in 传参
</method>

int age() const

1
2
3
<method name=\"age\">
  <arg type=\"i\" direction=\"out\"/> // out 返回值
</method>

void setName(QString name)

1
2
3
<method name=\"setName\">
  <arg type=\"s\" direction=\"in\"/>
</method>

void setNames(QStringList names)

1
2
3
<method name=\"setNames\">
<arg type=\"as\" direction=\"in\"/>
</method>

void setNames(QByteArray ba)

1
2
3
<method name=\"setNames\">
<arg type=\"ay\" direction=\"in\"/>
</method>

自定义类型

类和结构体, 不允许空结构体

1
2
3
4
5
struct MyStruct
{
    int key;
    QString value;
}

void setCustom(MyStruct my)

1
2
3
<method name=\"setCustom\">
  <arg type=\"(is)\" direction=\"in\"/>
</method

void setCustoms(QList<MyStruct> mystructs)

1
2
3
<method name=\"setCustoms\">
  <arg type=\"a(is)\" direction=\"in\"/>
</method>
1
2
3
4
struct MyStruct
{
    QMap<QString, QVariant> maps;
}

void setCustom(MyStruct my)

1
2
3
<method name=\"setCustom\">
  <arg type=\"(a{sv})\" direction=\"in\"/>
</method>

键值队
void setDict(QMap<int, QString> dict);

1
2
3
<method name=\"setDict\">
  <arg type=\"a{is}\" direction=\"in\"/>
</method>

权限控制

通过配置文件来实现权限控制。

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
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
    <type>session/system</type>
    <policy context="default">
        <deny send_destination="com.brion.service"/>
    </policy>
    <policy user="service">
        <!-- Only service user can own the "com.brion.service" service -->
        <allow own="com.brion.service"/>
        <allow send_destination="com.brion.service"/>
    </policy>
    <policy user="system">
        <allow send_destination="com.brion.service"/>
    </policy>
    <policy group="systemapps">
        <allow send_destination="com.brion.service" send_interface="com.brion.interface" send_member="setAge" send_path="/" send_type="method_call"/>
        <allow send_destination="com.brion.service" send_interface="com.brion.interface" send_member="ageChanged" send_path="/" send_type="signal"/>
        <deny send_destination="com.brion.service" send_interface="com.brion.service" send_member="age" send_path="/" send_type="method_call"/>
        <deny send_destination="com.brion.service" send_interface="com.brion.interface" send_member="nameChanged" send_path="/" send_type="signal"/>
    </policy>
    <policy at_console="true">
        <allow send_destination="xxx.xx.xx"/>
        <allow send_interface="xxx.xx.xx"/>
    </policy>
</busconfig>

所有 context=”default” 的策略被应用
所有 group=”connection’s user’s group” 的策略以不定的顺序被应用
所有 user=”connection’s auth user” 的策略以不定顺序被应用
所有 at_console=”true” 的策略被应用
所有 at_console=”false” 的策略被应用
所有 context=”mandatory” 的策略被应用
后应用的策略会覆盖前面的策略。

dbus 常用命令

dbus-send

调用函数

dbus-send --session --type=method_call  --dest=com.brion.service / com.brion.interface.setName "string:Brion"

发送信号

dbus-send --session --type=signal --dest=com.brion.service / com.brion.interface.ageChanged int32:10000

dbus-monitor

监听dbus-daemon的行为

手动启动dbus-daemon

 DBUS_VERBOSE=1 dbus-daemon --session --print-address

启动后会得到一个地址

unix:abstract=/tmp/dbus-YcjSNNPJHg,guid=18b385acdbd58611ffd3196b4beb69f0

设置环境变量 DBUS_SESSION_BUS_ADDRESS

DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-YcjSNNPJHg,guid=18b385acdbd58611ffd3196b4beb69f0

再启动dbus server 和dbus client 都会用这个dbus-daemon 来通信。

 

附:示例代码:

dbus-demo