DBus for IPC in Qt

Linux 系统IPC种类:

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

DBus 概念

总线

持久化的系统总线(system bus)
会话总线(session bus)

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仅仅是为了用来实现绑定。

优点

D-Bus是低延迟而且低开销的,设计得小而高效,以便最小化传送的往返时间。
另外,协议是二进制的,而不是文本的,这样就排除了费时的序列化过程。

D-Bus进程通信简单框架

D-Bus进程通信简单框架

Create DBus

获取system bus

QDBusConnection systemBus = QDBusConnection::systemBus();

获取session bus

QDBusConnection sessionBus = QDBusConnection::sessionBus();

注册服务

bool QDBusConnection::registerService ( const QString & serviceName )

注册对象接口

bool QDBusConnection::registerObject ( const QString & path, QObject * object, 
                                      RegisterOptions options = ExportAdaptors )

//常用Option选项
QDBusConnection::ExportAdaptors
QDBusConnection::ExportNonScriptableSlots
QDBusConnection::ExportNonScriptableSignals
QDBusConnection::ExportScriptableProperties
QDBusConnection::ExportNonScriptableInvokables // Q_INVOKABLE

QDBusConnection::ExportAllContents = 
QDBusConnection::ExportNonScriptableSlots|QDBusConnection::ExportNonScriptableSignals
|QDBusConnection::ExportNonScriptableInvokables|QDBusConnection::ExportScriptableProperties

例子:

Dbus Server

// 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

#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"

客户端调用服务端的函数

方式一

  // 传参数
  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不仅可以用来存储发送的参数,也用来存储返回值
      // 这里取得 checkIn 的返回值
      QString name= response.arguments().takeFirst().toString();
  }

方式二

    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 ;
    }

方式三: 异步调用

 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();
 }

客户端接收服务端信号

方式一:

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

方式二:


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
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

自定义类型

方式一:

 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实现自定义类型

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来控制接口

// 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
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)

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

int age() const

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

void setName(QString name)

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

void setNames(QStringList names)

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

void setNames(QByteArray ba)

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

// 有待测试

自定义类型

类和结构体, 不允许空结构体
struct MyStruct
{
int key;
QString value;
}

void setCustom(MyStruct my)

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

void setCustoms(QList<MyStruct> mystructs)

<method name=\"setCustoms\">
  <arg type=\"a(is)\" direction=\"in\"/>
</method>

struct MyStruct
{
QMap<QString, QVariant> maps;
}

void setCustom(MyStruct my)

<method name=\"setCustom\">
  <arg type=\"a(sv)\" direction=\"in\"/>
</method>

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

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

权限控制

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

<!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 来通信。

参考:
http://blog.csdn.net/mznewfacer/article/details/7475265

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容