linux+Qt 下利用D-Bus进行进程间高效通信的三种方式
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进行进程间高效通信的三种方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求电影名字具体忘了
- 下一篇: 联想拯救者Y9000-ubuntu-nv