转载请注明出处 https://blog.csdn.net/czhzasui/article/details/81071383
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
每个对象都有两种成员:信号和方法。信号是从对象广播到对象的任何感兴趣的观察者; 信号可以包含数据有效载荷。方法是可以在对象上调用的操作,具有可选输入(也称为参数或“参数”)和输出(也称为返回值或“输出参数”)。方法和信号都是通过名称引用。
当通过总线进行通信时,应用程序获得所谓的“服务名称”:该应用程序选择在同一总线上选择为其他应用程序所知。服务名称由D-Bus总线守护程序代理,用于将消息从一个应用程序路由到另一个应用程序。
D-Bus服务名称的格式与主机名非常相似:它是以点分隔的字母和数字序列。通常的做法是根据定义该服务的组织的域名来命名一个服务名称。
QDBusConnection::sessionBus().registerService("com.citos.DBus");
总线中信息流向的端点在D-Bus中称为对象。对象由客户进程创建,并在连接进程中保持不变。对象是客户进程提供服务的方式,当然客户进程可以创建多个对象。
QDBusConnection::sessionBus().registerObject("/com/citos/path", this);
D-Bus中的对象路径形成类似于文件系统上的路径名:它们是斜杠分隔的标签,每个标签由字母,数字和下划线字符(“_”)组成。它们必须始终以斜线开头,并且不能以斜杠结束。
我们已经知道,每个对象都具有一些方法、以及可以产生特定的信号。方法与信号的集合称为这个对象的成员。
对象的所有成员都通过接口来定义。与JAVA类似,接口就是成员声明的集合。接口的实现就等于说这个接口提供了其所有方法及信号。所有成员必须按照接口声明的那样来提供服务。
当客户进程要执行一个对象方法或等待信号时,它必须指明要使用的对象及其对象成员。除此以外,客户可能还要指明对象成员所在的接口,有时这是必须的步骤。比如,当对象同时在其两个接口都实现foo方法时,若客户不指明要执行的是哪个接口上的foo方法,则会产生歧义。
Q_CLASSINFO("D-Bus Interface", "com.citos.test")
QDBusConnection代表到D-Bus总线的一个连接,是一个D-Bus会话的起始点。通过QDBusConnection连接对象,可以访问远程对象、接口,连接远程信号到本地槽函数,注册对象等。
作为两种最常用总线类型的辅助,sessionBus()和systemBus()函数分别创建到会话在总线和系统总线的连接并返回,会在初次使用时打开,在QCoreApplication析构函数调用时断开。
QDBusMessage类表示D-Bus总线发送或接收的一个消息。QDBusMessage对象代表总线上四种消息类型中的一种,四种消息类型如下:
A、Method calls
B、Method return values
C、Signal emissions
D、Error codes
可以使用静态函数createError()、createMethodCall()、createSignal()创建消息。使用QDBusConnection::send() 函数发送消息。
QDBusInterface是远程对象接口的代理。QDBusInterface是一种通用的访问器类,用于调用远程对象,连接到远程对象导出的信号,获取/设置远程属性的值。当没有生成表示远程接口的生成代码时时,QDBusInterface类对远程对象的动态访问非常有用。
调用通常是通过使用call()函数来实现,call函数构造消息,通过总线发送消息,等待应答并解码应答。信号使用QObject::connect()函数进行连接。最终,使用QObject::property()和QObject::setProperty()函数对属性进行访问。
QDBusReply类用于存储对远程对象的方法调用的应答。一个QDBusReply对象是方法调用的应答QDBusMessage对象的一个子集。QDBusReply对象只包含第一个输出参数或错误代码,并由QDBusInterface派生类使用,以允许将错误代码返回为函数的返回参数。
QDBusAbstractAdaptor类是用于使用D-Bus向外部提供接口的所有对象的起点。可以通过将一个或多个派生自QDBusAbstractAdaptor的类附加到一个普通QObject对象上,使用QDBusConnection::registerObject注册QObject对象可以实现。QDBusAbstractAdaptor是一个轻量级封装,主要用于中继调用实际对象及其信号。每个QDBusAbstractAdaptor派生类都应该使用类定义中Q_CLASSINFO宏来定义D-Bus接口。注意,这种方式只有一个接口可以暴露。
QDBusAbstractAdaptor使用了信号、槽、属性的标准QObject机制来决定哪些信号、槽、属性被暴露到总线。任何QDBusAbstractAdaptor派生类发送的信号通过任何D-Bus连接自动中继到注册的对象上。QDBusAbstractAdaptor派生类对象必须使用new创建在堆上,不必由用户删除。
QDBusAbstractInterface是QtDBus模块中允许访问远程接口的所有D-Bus接口的基类。自动生成的代码类也继承自QDBusAbstractInterface,此描述的所有方法在生成的代码中也有效。除了此处的描述,生成代码类为远程方法提供了成员函数,允许在编译时检查正确参数和返回值,以及匹配的属性类型和匹配的信号参数。
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>
D-feet是一个非常实用的python工具,用来查看linux下DBus通讯。
安装方式:
sudo apt install d-feet
使用方法:
输入DBus service名字,就能找到该service并查看
QtDbus中的信号(signal)由单个消息组成,由一个进程发送到任意数量的其他进程。也就是说,信号是单向广播。信号可以包含参数,但因为它是广播,所以没有返回值。与方法调用(method)相比,方法调用消息具有匹配的方法回复消息。
信号的发射者不知道信号接收者。接受者向总线守护程序注册以根据“匹配规则”接收信号,这些规则通常包括interface和信号名称,此外还可以用service name和object path加以限定。
QtDbus中的信号发生过程如下:
1.接收者通过槽(Slot)绑定信号,指定匹配规则。
2.发送者创建信号消息并将其发送到总线守护程序。
3.总线守护程序检查信号并确定哪些接收者对其感兴趣。它将信号消息发送到这些进程。
下面这个例程实现了客户端和服务器信号的双向发送接收。
头文件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();
}
头文件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();
}
客户端按下Enter,服务器会显示消息
这个”Hello world!“字符串是由客户端发送给服务器的signal携带的数据,当然signal也可以不携带任何数据:
在服务器端输入Enter,客户端就会收到一个没有数据载荷的信号send_to_
client,显示消息:
发送信号
(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.1例程可以看到使用Signal需要调用QDBusMessage类。使用Method相对于Signal而言方式更多一些,QDBusMessage、QDBusInterface类都可以实现。
DBus中的方法调用由两个消息组成:从进程A发送到进程B的方法调用消息,以及从进程B发送到进程A的匹配方法应答消息。调用和应答消息都通过总线守护进程。呼叫者在每个呼叫消息中包括不同的序列号,并且回复消息包括该号码以允许呼叫者匹配对呼叫的回复。
总线守护程序永远不会重新排序消息。也就是说,如果您向同一个收件人发送两个方法调用消息,它们将按照发送顺序收到。
通过调用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);
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通讯。
头文件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();
}
头文件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();
}
通过D-Feet查看Session bus,可以看到有两个Method分别为interfaceMe_thod和messageMethod。
以interfaceMethod为例,点击该函数出现弹框,输入123,注意因为是字符串,要添加双引号。点击运行,会发现输出:’Get interfaceMethod reply: 123’,因为这是该method的返回值。
使用工具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
下面是一个非常简单的例程,
头文件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();
}
头文件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>