QtDBus总结

转载请注明出处 https://blog.csdn.net/czhzasui/article/details/81071383

一、QtDBus简介

  D-Bus是一种高级的进程间通信机制,它由freedesktop.org项目提供,使用GPL许可证发行。D-Bus最主要的用途是在Linux桌面环境为进程提供通信,同时能将Linux桌面环境和Linux内核事件作为消息传递到进程。注册后的进程可通过总线接收或传递消息,进程也可注册后等待内核事件响应,例如等待网络状态的转变或者计算机发出关机指令。目前,D-Bus已被大多数Linux发行版所采用,开发者可使用D-Bus实现各种复杂的进程间通信任务。
  D-Bus是一个消息总线系统,其功能已涵盖进程间通信的所有需求,并具备一些特殊的用途。D-Bus是三层架构的进程间通信系统,其中包括:
  接口层:接口层由函数库libdbus提供,进程可通过该库使用D-Bus的能力。
  总线层:总线层实际上是由D-Bus总线守护进程提供的。它在Linux系统启动时运行,负责进程间的消息路由和传递,其中包括Linux内核和Linux桌面环境的消息传递。
  包装层:包装层一系列基于特定应用程序框架的Wrapper库。
  在QT中的Dbus是使用的Dbus的包装层libdbus-qt.要查看Dbus总线上的服务和对象可以借助D-Feet工具 。
要使用QtDBus模块,需要在代码中加入以下代码:

#include 

如果使用qmake构建程序,需在工程文件中增加下列代码来链接QtDBus库:

QT += qdbus

二、QtDBus概念

2.1 Signal and Method(信号和方法)

  每个对象都有两种成员:信号和方法。信号是从对象广播到对象的任何感兴趣的观察者; 信号可以包含数据有效载荷。方法是可以在对象上调用的操作,具有可选输入(也称为参数或“参数”)和输出(也称为返回值或“输出参数”)。方法和信号都是通过名称引用。

2.2 Service Name(服务名称)

  当通过总线进行通信时,应用程序获得所谓的“服务名称”:该应用程序选择在同一总线上选择为其他应用程序所知。服务名称由D-Bus总线守护程序代理,用于将消息从一个应用程序路由到另一个应用程序。
D-Bus服务名称的格式与主机名非常相似:它是以点分隔的字母和数字序列。通常的做法是根据定义该服务的组织的域名来命名一个服务名称。

QDBusConnection::sessionBus().registerService("com.citos.DBus");

2.3 Object Path(对象路径)

  总线中信息流向的端点在D-Bus中称为对象。对象由客户进程创建,并在连接进程中保持不变。对象是客户进程提供服务的方式,当然客户进程可以创建多个对象。

QDBusConnection::sessionBus().registerObject("/com/citos/path", this);

  D-Bus中的对象路径形成类似于文件系统上的路径名:它们是斜杠分隔的标签,每个标签由字母,数字和下划线字符(“_”)组成。它们必须始终以斜线开头,并且不能以斜杠结束。

2.4 Interface(接口)

  我们已经知道,每个对象都具有一些方法、以及可以产生特定的信号。方法与信号的集合称为这个对象的成员。
对象的所有成员都通过接口来定义。与JAVA类似,接口就是成员声明的集合。接口的实现就等于说这个接口提供了其所有方法及信号。所有成员必须按照接口声明的那样来提供服务。
  当客户进程要执行一个对象方法或等待信号时,它必须指明要使用的对象及其对象成员。除此以外,客户可能还要指明对象成员所在的接口,有时这是必须的步骤。比如,当对象同时在其两个接口都实现foo方法时,若客户不指明要执行的是哪个接口上的foo方法,则会产生歧义。

Q_CLASSINFO("D-Bus Interface", "com.citos.test")

三、QtDBus常用类

3.1 QDBusConnection

  QDBusConnection代表到D-Bus总线的一个连接,是一个D-Bus会话的起始点。通过QDBusConnection连接对象,可以访问远程对象、接口,连接远程信号到本地槽函数,注册对象等。
作为两种最常用总线类型的辅助,sessionBus()和systemBus()函数分别创建到会话在总线和系统总线的连接并返回,会在初次使用时打开,在QCoreApplication析构函数调用时断开。

3.2 QDBusMessage

  QDBusMessage类表示D-Bus总线发送或接收的一个消息。QDBusMessage对象代表总线上四种消息类型中的一种,四种消息类型如下:
A、Method calls
B、Method return values
C、Signal emissions
D、Error codes
  可以使用静态函数createError()、createMethodCall()、createSignal()创建消息。使用QDBusConnection::send() 函数发送消息。

3.3 QDBusInterface

  QDBusInterface是远程对象接口的代理。QDBusInterface是一种通用的访问器类,用于调用远程对象,连接到远程对象导出的信号,获取/设置远程属性的值。当没有生成表示远程接口的生成代码时时,QDBusInterface类对远程对象的动态访问非常有用。
调用通常是通过使用call()函数来实现,call函数构造消息,通过总线发送消息,等待应答并解码应答。信号使用QObject::connect()函数进行连接。最终,使用QObject::property()和QObject::setProperty()函数对属性进行访问。

3.4 QDBusReply

  QDBusReply类用于存储对远程对象的方法调用的应答。一个QDBusReply对象是方法调用的应答QDBusMessage对象的一个子集。QDBusReply对象只包含第一个输出参数或错误代码,并由QDBusInterface派生类使用,以允许将错误代码返回为函数的返回参数。

3.5 QDBusAbstractAdaptor

  QDBusAbstractAdaptor类是用于使用D-Bus向外部提供接口的所有对象的起点。可以通过将一个或多个派生自QDBusAbstractAdaptor的类附加到一个普通QObject对象上,使用QDBusConnection::registerObject注册QObject对象可以实现。QDBusAbstractAdaptor是一个轻量级封装,主要用于中继调用实际对象及其信号。每个QDBusAbstractAdaptor派生类都应该使用类定义中Q_CLASSINFO宏来定义D-Bus接口。注意,这种方式只有一个接口可以暴露。
  QDBusAbstractAdaptor使用了信号、槽、属性的标准QObject机制来决定哪些信号、槽、属性被暴露到总线。任何QDBusAbstractAdaptor派生类发送的信号通过任何D-Bus连接自动中继到注册的对象上。QDBusAbstractAdaptor派生类对象必须使用new创建在堆上,不必由用户删除。

3.6 QDBusAbstractInterface

  QDBusAbstractInterface是QtDBus模块中允许访问远程接口的所有D-Bus接口的基类。自动生成的代码类也继承自QDBusAbstractInterface,此描述的所有方法在生成的代码中也有效。除了此处的描述,生成代码类为远程方法提供了成员函数,允许在编译时检查正确参数和返回值,以及匹配的属性类型和匹配的信号参数。

四、QtDBus工具

4.1 qdbuscpp2xml

  qdbuscpp2xml会解析QObject派生类的C++头文件或是源文件,生成D-Bus的内省xml文件。qdbuscpp2xml 会区分函数的输入输出,如果参数声明为const则会是输入,否则可能会被当作输出。
  qdbuscpp2xml使用语法如下:

qdbuscpp2xml [options...] [files...]

  Options参数如下:

    -p|-s|-m:只解析脚本化的属性、信号、方法(槽函数)
    -P|-S|-M:解析所有的属性、信号、方法(槽函数)
    -a:输出所有的脚本化内容,等价于-psm
    -A:输出所有的内容,等价于-PSM
    -o filename:输出内容到filename文件

  通常利用xml语言自动生成QDBusAbstractAdaptor和QDBusAbstractInterface两个派生类adaptor和interface,对应的.c和.h文件可以在编译生成的文件夹中找到。

<node>
  <interface name="org.example.chat">
    <signal name="message">
      <arg name="nickname" type="s" direction="out"/>
      <arg name="text" type="s" direction="out"/>
 signal>
    <signal name="action">
      <arg name="nickname" type="s" direction="out"/>
      <arg name="text" type="s" direction="out"/>
    signal>
  interface>
node>

4.2 D-Feet工具

  D-feet是一个非常实用的python工具,用来查看linux下DBus通讯。
  安装方式:

sudo apt install d-feet 

  使用方法:
  输入DBus service名字,就能找到该service并查看
QtDBus总结_第1张图片

五、QtDBus编程

5.1 Signal通讯

  QtDbus中的信号(signal)由单个消息组成,由一个进程发送到任意数量的其他进程。也就是说,信号是单向广播。信号可以包含参数,但因为它是广播,所以没有返回值。与方法调用(method)相比,方法调用消息具有匹配的方法回复消息。
信号的发射者不知道信号接收者。接受者向总线守护程序注册以根据“匹配规则”接收信号,这些规则通常包括interface和信号名称,此外还可以用service name和object path加以限定。
  QtDbus中的信号发生过程如下:
1.接收者通过槽(Slot)绑定信号,指定匹配规则。
2.发送者创建信号消息并将其发送到总线守护程序。
3.总线守护程序检查信号并确定哪些接收者对其感兴趣。它将信号消息发送到这些进程。

 下面这个例程实现了客户端和服务器信号的双向发送接收。

5.1.1 Client

头文件client.h

#ifndef CLIENT_H
#define CLIENT_H
#include 
#include 
#include 
#include 
#include 
#include 

class Client: public QObject
{
    Q_OBJECT
public:
    Client();
public slots:
    void client_get(void);
signals:
    void send_to_service(QString st);
};

#endif // CLIENT_H

  主程序client.cpp负责信号绑定(connect),还创建一个新的线程。主线程负责接收信号,新线程负责检测按键输入、发送信号。

#include "client.h"

Client::Client()
{
    QDBusConnection::sessionBus().connect(QString(), QString("/citos/path"), "com.citos.test", "send_to_client", this, SLOT(client_get(void)));
}

void Client::client_get()
{
    qDebug() << "Client get!";
}

void client_listen()
{
    while(true)
    {
        getchar();
        qDebug() << "Message send!";
        QDBusMessage message =QDBusMessage::createSignal("/citos/path", "com.citos.test", "send_to_service");
        message << QString("Hello world!");
        QDBusConnection::sessionBus().send(message);
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Client client;

    std::thread th1(client_listen);
    th1.detach();

    return a.exec();
}

5.1.2 Service

头文件service.h

#ifndef SERVICE_H
#define SERVICE_H
#include 
#include 
#include 
#include 
#include 
#include 
class Service: public QObject
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.citos.test")
public:
    Service();
public slots:
    void service_get(QString st);
signals:
    void send_to_client(void);
};

#endif // SERVICE_H

主程序service.cpp

#include "service.h"

Service::Service()
{
    QDBusConnection::sessionBus().unregisterService("com.citos.service");
    QDBusConnection::sessionBus().registerService("com.citos.service");
    QDBusConnection::sessionBus().registerObject("/citos/path", this,QDBusConnection :: ExportAllSlots | QDBusConnection :: ExportAllSignals);
    QDBusConnection::sessionBus().connect(QString(), QString("/citos/path"), "com.citos.test", "send_to_service", this, SLOT(service_get(QString)));
}
void Service::service_get(QString st)
{
    qDebug() << "Message get from client: "<< st;
}

void service_listen()
{
    while(true)
    {
        getchar();
        qDebug() << "Message send!";
        QDBusMessage message =QDBusMessage::createSignal("/citos/path", "com.citos.test", "send_to_client");
        QDBusConnection::sessionBus().send(message);
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Service service;

    std::thread th1(service_listen);
    th1.detach();

    return a.exec();
}

  启动程序后,通过D-Feet查看Session bus
  QtDBus总结_第2张图片

  客户端按下Enter,服务器会显示消息
这里写图片描述
  这个”Hello world!“字符串是由客户端发送给服务器的signal携带的数据,当然signal也可以不携带任何数据:
在服务器端输入Enter,客户端就会收到一个没有数据载荷的信号send_to_
client,显示消息:
这里写图片描述

5.1.3 Signal总结

发送信号
(1)创建QT的DBus信号

QDBusMessage message =QDBusMessage::createSignal("/citos/path", "com.citos.test", "send_to_service");

(2)给信号赋值

message << QString("Hello world!");

(3)发射信号

QDBusConnection::sessionBus().send(message);

接收信号
(1)绑定信号

QDBusConnection::sessionBus().connect(QString(), QString("/citos/path"), "com.citos.test",\
 "send_to_service", this, SLOT(service_get(QString)));

(2)在槽中进行相关的处理

void Service::service_get(QString st)
{
    qDebug() << "Message get from client: "<< st;
}

5.2 Method通讯

  由5.1例程可以看到使用Signal需要调用QDBusMessage类。使用Method相对于Signal而言方式更多一些,QDBusMessage、QDBusInterface类都可以实现。
  DBus中的方法调用由两个消息组成:从进程A发送到进程B的方法调用消息,以及从进程B发送到进程A的匹配方法应答消息。调用和应答消息都通过总线守护进程。呼叫者在每个呼叫消息中包括不同的序列号,并且回复消息包括该号码以允许呼叫者匹配对呼叫的回复。
  总线守护程序永远不会重新排序消息。也就是说,如果您向同一个收件人发送两个方法调用消息,它们将按照发送顺序收到。

5.2.1 使用QDBusMessage

通过调用QDBusMessage的createMethodCall函数可以远程调用对象的method。流程如下:
发送方
1.创建一个QDBusMessage的method调用

QDBusMessage msg = QDBusMessage::createMethodCall( "com.citos.service",
                                                "/citos/path",
                                                "com.citos.test",
                                                "messageMethod");

2.给method传递参数

QTextStream qin(stdin);
QString str;
qDebug() << "$:";
qin >> str;
msg << str;

3.调用method

QDBusMessage response = QDBusConnection::sessionBus().call(msg);

4.判断method的返回值

if (response.type() == QDBusMessage::ReplyMessage)
    {
        qDebug() << response.arguments().takeFirst().toString();
    }

接收方
1.注册槽函数

public slots:
    QString messageMethod(const QString &arg);

2.在槽中进行相关处理

QString Pong::messageMethod(const QString &arg)
{
    return QString("Get messageMethod reply: %1").arg(arg);
}

3.注册对象路径,导出所有此对象的插槽

QDBusConnection::sessionBus().registerObject("/citos/path", this, \
                                QDBusConnection::ExportAllSlots);

5.2.2 使用QDBusInterface

QDBusInterface类也能实现method通讯。流程如下:
发送方
1.创建一个QDBusInterface的实例

QDBusInterface iface("com.citos.service", "/citos/path", "com.citos.test", \
                                            QDBusConnection::sessionBus());

2.给method传递参数

QTextStream qin(stdin);
QString str;
qDebug() << "$:";
qin >> str;

3.调用method

QDBusReply reply = iface.call("interfaceMethod", str);

4.判断method返回值

if (reply.isValid()) {
qDebug() << reply.value();
return ;
}
else{
    qDebug() << QString("Call failed");
}

接收方
1.注册槽函数

public slots:
    QString interfaceMethod(const QString &arg);

2.在槽中进行相关处理

QString Pong::interfaceMethod(const QString &arg)
{
    return QString("Get interfaceMethod reply: %1").arg(arg);
}

3.注册对象路径,导出所有此对象的插槽

QDBusConnection::sessionBus().registerObject("/citos/path", this, \
                                QDBusConnection::ExportAllSlots);

下面这个例程,同时使用了QDBusMessage和QDBusInterface类,实现了客户端和服务器信号的method通讯。

5.2.3 Client

头文件client.h

#ifndef PING_H
#define PING_H
#include 
#include 
#include 
#include 
#include 

class Ping: public QObject
{
    Q_OBJECT
public:
    Ping();
    void send();
    void time();
public slots:
signals:
    void ping(void);
};

#endif // PING_H

主程序中通过输入msg或者ifc选择QDBusMessage或是QDBusInterface实现method通讯。

#include "client.h"

void test_message(){
    QTextStream qin(stdin);
    QString str;
    qDebug() << "$:";
    qin >> str;

    QDBusMessage msg = QDBusMessage::createMethodCall("com.citos.service",
                               "/citos/path",
                               "com.citos.test",
                               "messageMethod");
    msg << str;

    QDBusMessage response = QDBusConnection::sessionBus().call(msg);
    if (response.type() == QDBusMessage::ReplyMessage)
    {
        qDebug() << response.arguments().takeFirst().toString();
    }
    else
    {
        qDebug() << response.arguments().takeFirst().toString();
    }
}

void test_interface(){
    QTextStream qin(stdin);
    QString str;
    qDebug() << "$:";
    qin >> str;

    QDBusInterface iface("com.citos.service", "/citos/path", "com.citos.test", QDBusConnection::sessionBus());
    if (iface.isValid()) {

        QDBusReply reply = iface.call("interfaceMethod", str);
        if (reply.isValid()) {
            qDebug() << reply.value();
            return ;
        }
        else{
            qDebug() << QString("Call failed");
        }
    }
    else{
        fprintf(stderr, "%s\n",qPrintable(QDBusConnection::sessionBus().lastError().message()));
    }
}

Ping::Ping()
{
    QDBusConnection::sessionBus().registerObject("/citos/path", this);
    //this->time();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTextStream qin(stdin);
    QString str;
    Ping ping;
    if (!QDBusConnection::sessionBus().isConnected()) {
            qDebug() << QString("Cannot connect to the bus daemon");
            return -1;
    }

    while(true){
        system("clear");
        qDebug() << "Please input order(msg/ifc)";
        qin >> str;
        if(str == "msg")
        {
            test_message();
        }
        if(str == "ifc")
        {
            test_interface();
        }
        qDebug() << "Input anything to continue.";
        getchar();

    }
    return a.exec();
}

5.2.4 Service

头文件service.h

#ifndef PONG_H
#define PONG_H

#include 
#include 
#include 
#include 
#include 

class Pong: public QObject
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.citos.test")
public:
    Pong();
public slots:
    QString interfaceMethod(const QString &arg);
    QString messageMethod(const QString &arg);
signals:
};

#endif // PONG_H

主程序service.cpp

#include "service.h"


QString Pong::interfaceMethod(const QString &arg)
{
    return QString("Get interfaceMethod reply: %1").arg(arg);
}


QString Pong::messageMethod(const QString &arg)
{
    return QString("Get messageMethod reply: %1").arg(arg);
}

Pong::Pong()
{
    QDBusConnection::sessionBus().unregisterService("com.citos.service");
    QDBusConnection::sessionBus().registerService("com.citos.service");
    QDBusConnection::sessionBus().registerObject("/citos/path", this, QDBusConnection :: ExportAllSlots);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Pong pong;

    return a.exec();
}

5.2.5 使用D-Feet工具调试

  通过D-Feet查看Session bus,可以看到有两个Method分别为interfaceMe_thod和messageMethod。
QtDBus总结_第3张图片
  以interfaceMethod为例,点击该函数出现弹框,输入123,注意因为是字符串,要添加双引号。点击运行,会发现输出:’Get interfaceMethod reply: 123’,因为这是该method的返回值。
QtDBus总结_第4张图片

5.3 D-Bus XML自动生成类

使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类,工程名为ping。过程如下。
(1)生成xml文件

qdbuscpp2xml -M ping.h -o ping.xml

(2)通过xml文件生成ping_adaptor.cpp和ping_adaptor.h

qdbusxml2cpp ping.xml -p ping_adaptor

使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类
(1)生成XML文件(这一步与上面第一步相同,可省略)

qdbuscpp2xml -M ping.h -o ping.xml 

(2)通过xml文件生成ping_adaptor.cpp和ping_adaptor.h

qdbusxml2cpp ping.xml -i ping.h -a ping_interface

此外还可以将xml文件添加到工程中完成上述第二步操作,在编译的时候会自动生成ping_adaptor.cpp、ping_adaptor.h、ping_adaptor.cpp和ping_adaptor.h。

DBUS_ADAPTORS += pong.xml
DBUS_INTERFACES += pong.xml

下面是一个非常简单的例程,

5.3.1 Client

头文件Ping.h

#ifndef PING_H
#define PING_H
#include 
#include 
#include 
#include 
#include 

class Ping: public QObject
{
    Q_OBJECT
public:
    Ping();
    void update();
signals:
    void ping(void);
};

#endif // PING_H

主程序ping.cpp

#include "ping.h"

void Ping::update()
{
    qDebug() << "send";
    QDBusMessage message =QDBusMessage::createSignal("/citos/path", "com.citos.test", "ping");
    QDBusConnection::sessionBus().send(message);
}

Ping::Ping()
{

}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTextStream qin(stdin);
    QString str;
    Ping ping;

    while(true){
        system("clear");
        qDebug() << "Please input order(ping)";
        qin >> str;
        if(str == "ping")
        {
            ping.update();
        }
        qDebug() << "Input anything to continue.";
        getchar();

    }
    return a.exec();
}

5.3.2 Service

头文件pong.h

#ifndef PONG_H
#define PONG_H

#include 
#include 
#include 
#include 
#include 

class Pong: public QObject
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.citos.test")
public:
    void send();
    Pong();
    void time();
public slots:
    void pong(void);
signals:
    void ping(void);
};

#endif // PONG_H

主程序pong.cpp

#include "pong.h"
#include "pong_adaptor.h"
#include "pong_interface.h"

void Pong::pong(void){
    qDebug() << QString("Receive");
    QDBusMessage message =QDBusMessage::createSignal("/citos/path", "com.citos.test2", "reping");
    QDBusConnection::sessionBus().send(message);
}

Pong::Pong()
{
    new TestAdaptor(this);
    QDBusConnection::sessionBus().unregisterService("com.citos.service");
    QDBusConnection::sessionBus().registerService("com.citos.service");
    QDBusConnection::sessionBus().registerObject("/citos/path", this, QDBusConnection :: ExportAllSlots | QDBusConnection :: ExportAllSignals);

    com::citos::test *test;
    test = new com::citos::test(QString(), QString(), QDBusConnection::sessionBus(), this);
    //QDBusConnection::sessionBus().connect(QString(), QString("/citos/path"), "com.citos.test", "ping", this, SLOT(pong(void)));
    connect(test, SIGNAL(ping(void)), this, SLOT(pong(void)));
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Pong pong;

    return a.exec();
}

该程序简单的实现了signal的单向发送,xml文件里定义了一个信号量ping(void)。

<node>
  <interface name="com.citos.test">
    <signal name="ping">
    signal>
  interface>
node>

运行效果如下:
客户端发送信号ping(void):
这里写图片描述
服务器接收到信号:
这里写图片描述

你可能感兴趣的:(DBus,Qt)