linux+Qt 下利用D-Bus进行进程间高效通信的三种方式

原文链接:
https://www.cnblogs.com/wwang/archive/2010/10/27/1862552.html

D-Bus概述
什么是D-Bus?

D-Bus是一种进程间通信的机制,它被设计成为一种低开销、低延迟的IPC,并被多种桌面环境(如KDE、GNOME等)所采用。
关于D-Bus的详细介绍可以参考freedesktop.org提供的两份文档, D-Bus Tutorial 和 D-Bus Specification 。
基本概念

D-Bus提供了多种Message Bus用于应用程序之间的通信。通常,Linux发行版都会提供两种Message Bus:System Bus和Session Bus。System Bus 主要用于内核和一些系统全局的service之间通信;Session Bus 主要用于桌面应用程序之间的通信。
D-Bus中用于通信的基本单元叫做Message,Message的具体格式可以参考 D-Bus Specification 。
当应用程序连接到M essage Bus上时,D-Bus会分配一个unique connection name,这个unique name通常的格式如":34-907"。Unique name以":“开头,后面的数字没有特别的意义,只是为了保证这个unique name的唯一性。
另外,应用程序还可以向Message Bus请求一个well-known name,格式如同一个反置的域名,例如"com.mycompany.myapp”。当一个应用程序连接到Message Bus上时,可以拥有两种名称:unique connection name和well-known name。这两种名称的关系可以理解为网络上的IP地址和域名的关系。
在D-Bus规范里,unique connection name和well-known name都叫做Bus Name。这点比较奇怪,也比较拗口,Bus Name并不是Message Bus的名称,而是应用程序和Message Bus之间的连接的名称。
应用程序和Message Bus之间的连接也被称为Service,这样一来,把Bus Name称作Service Name在概念上会更清晰一点。
当应用程序连接到Message Bus上时,该应用程序可以在Bus上创建一到多个Object(我们可以把D-Bus的object理解成面向对象语言里的object)。Service通过Object 为其他应用程序提供访问接口。因为在Message Bus上,一个应用程序可以对应多个Object,所以不同的Object必须由Object Path(类似于文件系统的路径)来区分。Object Path的格式如"/foo/bar"。
对于Service Name和Object Path,QT4文档中有一个类比还是比较直观的,如下图所示:

图中的ftp.example.com可以看作是Service Name,/pub/something可以看作是Object Path。

D-Bus通过Signal/Method来发送和接收Message。Signal/Method可以理解为QT4中的Signal/Slot这个概念。一个Object可以提供多个Method/Signal,这些Method/Signal的集合又组成了Interface。

因此,D-Bus的这些概念从大到小可以表示为:Message Bus->Service->Object->[Interface]->Method/Signal。

其中,Interface是可选的。

D-Bus 调试工具

常用的D-Bus调试工具有 D-Feet、qdbusviewer等。
在C onsole窗口中键入qdbusviewer命令可以打开QT自带的qdbusviewer 。

如上图所示,我们可以通过qdbusviewer来调用Object在Message Bus上发布的所有Method。

D -Bus 的 QT4 绑定

下面,我们通过一个实例来介绍D-Bus的QT4绑定。(参见hotel.pro)
我们在Session bus上创建一个"com.test.hotel" service,通过这个service可以执行check in,check out和query三个动作。

注: 一个通过service的名称来确定进程,即每个进程应该有一个自己的service,而不是多进程共用一个service,我在初期就犯了这个错,想通过在一个service下注册不同的Object来区别各个进程,

实际上这是不可行的,Object的作用是用来区分一个进程内多个通信对象的Object, service名称不可相同, 如果你只有一个进程间需要通信的DbusObject, 例如:

你在两个进程里分别注册 名字为 com.zyl.test 和 com.zyl.test1 的两个service, 然后分别在各自的service中 注册 名字为/testObject 的dbus 对象, 然后创建 在com.zyl.test 的进程中创建

service name 为com.zyl.test1 , path 为 /testObject 的 接口对象, 在com.zyl.test1 的进程中创建service name 为com.zyl.test , path 为 /testObject 的 接口对象, 即可实现双方的互相调用

注册service

注册Object

创建proxy代理对象并且调用

创建Service并且注册Object

int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 用于建立到session bus的连接QDBusConnection bus = QDBusConnection::sessionBus();// 在session bus上注册名为"com.test.hotel"的serviceif (!bus.registerService("com.test.hotel")) {qDebug() << bus.lastError().message();exit(1);}Hotel my_hotel;// 注册名为"/hotel/registry"的object。// "QDBusConnection::ExportAllSlots"表示把类Hotel的所有Slot都导出为这个Object的methodbus.registerObject("/hotel/registry", &my_hotel,QDBusConnection::ExportAllSlots);return a.exec();
}

我们再看一下Hotel类的定义。

class Hotel : public QObject
{Q_OBJECT// 定义Interface名称为"com.test.hotel.registry"Q_CLASSINFO("D-Bus Interface", "com.test.hotel.registry")
public:Hotel() { m_rooms = MAX_ROOMS; }
public slots:// Check in,参数为房间数,返回成功拿到的房间数int checkIn(int num_room);// Check out,参数为房间数,返回成功退回的房间数int checkOut(int num_room);// Query,用于查询目前还剩下的房间数int query();
private:int m_rooms;QReadWriteLock m_lock;
};

运行这个程序,我们可以使用qdbusviewer查看和操作这个Object。

通过QDBusMessage访问Service
在QT4中,用QDBusMessage表示在D-Bus上发送和接收的Message。(参见checkin.pro)

// 用来构造一个在D-Bus上传递的Message
QDBusMessage m = QDBusMessage::createMethodCall("com.test.hotel","/hotel/registry","com.test.hotel.registry","checkIn");
if (argc == 2) {// 给QDBusMessage增加一个参数;// 这是一种比较友好的写法,也可以用setArguments来实现m << QString(argv[1]).toInt();
}
// 发送Message
QDBusMessage response = QDBusConnection::sessionBus().call(m);
// 判断Method是否被正确返回
if (response.type() == QDBusMessage::ReplyMessage) {// QDBusMessage的arguments不仅可以用来存储发送的参数,也用来存储返回值;// 这里取得checkIn的返回值int num_room = response.arguments().takeFirst().toInt();printf("Got %d %s\n", num_room, (num_room > 1) ? "rooms" : "room");
} else {fprintf(stderr, "Check In fail!\n");
}

通过QDBusInterface 访问Service

在QT4中,QDBusInterface可以更加方便的访问Service。(参见checkin2.pro)

// 创建QDBusInterface
QDBusInterface iface( "com.test.hotel", "/hotel/registry","com.test.hotel.registry", QDBusConnection::sessionBus());
if (!iface.isValid()) {qDebug() << qPrintable(QDBusConnection::sessionBus().lastError().message());exit(1);
}
// 呼叫远程的checkIn,参数为num_room
QDBusReply<int> reply = iface.call("checkIn", num_room);
if (reply.isValid()) {num_room = reply.value();printf("Got %d %s\n", num_room, (num_room > 1) ? "rooms" : "room");
} else {fprintf(stderr, "Check In fail!\n");
}

看,用QDBusInterface来访问Service是不是更加方便?

从D-Bus XML自动生成Proxy类

用QDB usInterface访问Service已经非常方便了,但还不够直观。还有没有更直观的方法,就像访问本地类成员变量的方式访问远程的method?答案是Proxy。

Proxy Object提供了一种更加直观的方式来访问Service,就好像调用本地对象的方法一样。

概括的说,达成上述目标需要分三步走:

(1)使用工具qdbuscpp2xml从hotel.h生成XML文件;

            qdbuscpp2xml -M hotel.h -o com.test.hotel.xml

(2)使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类;

            qdbusxml2cpp com.test.hotel.xml -i hotel.h -p hotelInterface
   这条命令会生成两个文件:hotelInterface.cpp和hotelInterface.h

(3)调用(2)生成的类来访问Service。

Qt 5.11里, 可以不再使用工具从xml来导出 继承自QDBusInterface的类,

只需在pro文件中添加xml文件名称则可自动导出并且识别

代理类的使用方式为

注意: 不可以将代理类作为成员变量在原始类中使用,这样会造成互相包含的编译问题,

我这里使用的是全局变量的形式,使用代理类的槽和本地的信号槽一样,甚至还可以有返回值

下面是举例(参见checkin3.pro ):

// 初始化自动生成的Proxy类com::test::hotel::registry
com::test::hotel::registry myHotel("com.test.hotel","/hotel/registry",QDBusConnection::sessionBus());
// 调用checkIn
QDBusPendingReply<int> reply = myHotel.checkIn(num_room);
// qdbusxml2cpp生成的Proxy类是采用异步的方式来传递Message,
// 所以在此需要调用waitForFinished来等到Message执行完成
reply.waitForFinished();
if (reply.isValid()) {num_room = reply.value();printf("Got %d %s\n", num_room, (num_room > 1) ? "rooms" : "room");
} else {fprintf(stderr, "Check In fail!\n");
}

使用Adapter注册Object

如前文所述,我们可以直接把class Hotel注册为Message Bus上的一个Object,但这种方式并不是QT4所推荐的。QT4推荐使用Adapter来注册Object。
很多情况下,我们可能只需要把我们定义的类里的方法有选择的发布到Message Bus上,使用Adapter可以很方便的实现这种意图。
以前文中的Hotel为例,假设我们只需要把checkIn和checkOut发布到Message Bus上,应该怎么办?

(1)使用工具 qdbuscpp2xml从hotel.h生成XML文件;

            qdbuscpp2xml -M hotel.h -o com.test.hotel.xml

(2)编辑com.test.hotel.xml,把其中的query部分去掉;
即去掉以下三条语句:
<method name="query"> <arg type="i" direction="out"/> </method>
(3)使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类;
qdbusxml2cpp com.test.hotel.xml -i hotel.h -a hotelAdaptor
这条命令会生成两个文件:hotelAdaptor.cpp和hotelAdaptor.h
(4)调用(3)生成的类来注册Object。

(参见hotel2.pro)

int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);QDBusConnection bus = QDBusConnection::sessionBus();Hotel myHotel;// RegistryAdaptor是qdbusxml2cpp生成的Adaptor类RegistryAdaptor myAdaptor(&myHotel);if (!bus.registerService("com.test.hotel")) {qDebug() << bus.lastError().message();exit(1);}bus.registerObject("/hotel/registry", &myHotel);return a.exec();
}

运行这个应用程序,我们从qdbusviewer上可以看到,只有checkIn和checkOut两个method被发布。如下图所示:

自动启动Service
D-Bus系统提供了一种机制可以在访问某个service时,自动把该程序运行起来。sr/share/dbus-1/services下面建立com.test.hotel.service文件,文件的内容如下:
[D-BUS Service]Name=com.test.hotelExec=/path/to/your/hotel
这样,我们在访问Hotel的method之前,就不必手动运行该应用程序了。

linux+Qt 下利用D-Bus进行进程间高效通信的三种方式相关推荐

  1. 查看redis进程_redis安装(启动三种方式)

    一.安装redis 第 1 步:下载redis安装包 [root@hadoop105 redis]# wget http://download.redis.io/releases/redis-4.0. ...

  2. 【Linux入门到精通系列讲解】Centos 7软件安装的三种方式

    centos 软件安装的三种方式 Linux下面安装软件的常见方法: 一.yum 替你下载软件 替你安装 替你解决依赖关系 点外卖 缺少的东西 外卖解决 1.方便 简单 2.没有办法深入修改 yum ...

  3. VC++中进程间相互通信的十一种方法

    进程通常被定义为一个正在运行的程序的实例,它由两个部分组成: 一个是 操作系统用来管理进程的内核对象.内核对象也是系统用来存放关于进程的统计信息的地方 另一个是地址空间,它包含所有的可执行模块或DLL ...

  4. linux fork脚本,在Shell脚本中调用另一个脚本的三种方式讲解

    先来说一下主要以下有几种方式: fork: 如果脚本有执行权限的话,path/to/foo.sh.如果没有,sh path/to/foo.sh. exec: exec path/to/foo.sh s ...

  5. Linux系统下xxx.deb、xxx.rpm、xxx.tar.gz三种软件包的区别及安装方法

    一. rpm(Redhat Linux Packet Manager)包-在红帽LINUX.SUSE.Fedora可以直接进行安装,但在Ubuntu中却无法识别: deb包-debian发行版的软件包 ...

  6. Qt使用invokeMethod反射机制实现进程间的通信

    对于Qt来说,UI线程是主线程,对于同一UI线程中对象的通信可以通过connect进行信号与槽关联来实现,但是当UI中对象A中的子线程B需要和另外UI对象C进行通信的时候,如果这个时候使用connec ...

  7. 利用有名管道实现进程间的通信

    1 /*****************************************************************2 * Copyright (C) 2018 FBI WARNI ...

  8. Arduino环境下对NodeMCU ESP8266将文件直接传入flash的三种方式

    flash存储简答介绍 参考:https://www.elecfans.com/consume/572040.html flash存储器又称闪存(快闪存储器),就其本质而言,flash存储器属于EEP ...

  9. Linux进程通信的四种方式——共享内存、信号量、无名管道、消息队列|实验、代码、分析、总结

    Linux进程通信的四种方式--共享内存.信号量.无名管道.消息队列|实验.代码.分析.总结 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须 ...

最新文章

  1. mysql实时监听canal+kafka
  2. Android Studio主题设置、颜色背景配置
  3. Java读取Excel文件转换成JSON并转成List——(七)
  4. MySQL数据库进阶(API的理解、Python对MySQL的接口)
  5. Shadow Map阴影贴图技术之探 【转】
  6. C语言系列之自增自减运算符的用法(二)
  7. Windows DNS Server 远程代码执行漏洞 (CVE-2021-24078) 的详细原理分析
  8. No project is easy, am I right?
  9. mysql在linux下诸多稀奇古怪的错误
  10. 设计模式之---解释器模式
  11. MarkDown(七)——LaTeX数学公式符号标记
  12. data/mysql_error_trace.inc,09-在线挑战详细攻略-《真的很简单》
  13. 站长之死:互联网界夹心层的梦想反思录
  14. 【会议记录】软件工程课程设计第一次会议
  15. 软件项目中的角色以及英文简称
  16. 基于MATLAB的手机定位精度评估算法的仿真
  17. Ubuntu安装微软Onedrive教程
  18. 论文阅读——基于语法语义融合学习的功能代码克隆检测
  19. 【Python实例学习】用Python的xlsxwriter模块操作Excel表格,包括写入数据、样式设置、插入图片等
  20. 疫情期间想做兼职增加收入,那你应该看看这四个网赚项目

热门文章

  1. html中锚点的应用【本页面跳转】
  2. 遍历Treeview每个节点并初始化(C#)
  3. firebug 的使用
  4. 《OpenCV3编程入门》学习笔记6 图像处理(三)形态学滤波(1):腐蚀与膨胀
  5. java regexp_java中使用regexp
  6. php编译7教程,PHP7 快速编译安装
  7. java 获取泛型的type,如何获取泛型的Type类型
  8. cryptojs php 互通_关于PHP7和CryptoJS的AES加密方式互通
  9. puml绘制思维导图_如何用ProcessOn画出漂亮的思维导图
  10. mysql 附近3公里的_mysql搜寻附近N公里内数据的实例