DBus作為一個輕量級的IPC被越來越多的平臺接受,用于進(jìn)程間通信或
進(jìn)程與內(nèi)核的通信。
DBus進(jìn)程間通信主要有三層架構(gòu):
1.底層接口層:主要是通過libdbus這個函數(shù)庫,給予系統(tǒng)使用DBus的能力。
2.總線層:主 要Message bus daemon這個總線守護(hù)進(jìn)程提供的,在Linux系統(tǒng)啟動時運行,負(fù)責(zé)進(jìn)程間的消息路由和傳遞,其中包括Linux內(nèi)核和Linux桌面環(huán)境的消息傳 遞??偩€守護(hù)進(jìn)程可同時與多個應(yīng)用程序相連,并能把來自一個應(yīng)用程序的消息路由到0或者多個其他程序。
3.應(yīng)用封裝層:通過一系列基于特定應(yīng)用程序框架將DBus的底層接口封裝成友好的Wrapper庫,供不同開發(fā)人員使用。比如libdbus-glib, libdbus-python.
如上圖所示,Bus Daemon
Process是運行在linux系統(tǒng)中的一個后臺守護(hù)進(jìn)程,dbus-daemon運行時會調(diào)用libdus的庫。Application
Process1代表的就是應(yīng)用程序進(jìn)程,通過調(diào)用特定的應(yīng)用程序框架的Wrapper庫與dbus-daemon進(jìn)行通信。
從上圖也可以看出來Application和Daemon中其實還是通過socket進(jìn)行通行。
DBus的三大優(yōu)點:低延遲,低開銷,高可用性。
*低延遲:DBus一開始就是用來設(shè)計成避免來回傳遞和允許異步操作的。因此雖然在Application和Daemon之間是通過socket實現(xiàn)的,但是又去掉了socket的循環(huán)等待,保證了操作的實時高效。
*低開銷:DBus使用一個二進(jìn)制的協(xié)議,不需要轉(zhuǎn)化成像XML這樣的文本格式。因為DBus是主要用來機(jī)器內(nèi)部的IPC,而不是為了網(wǎng)絡(luò)上的IPC機(jī)制而準(zhǔn)備的.所以它才能夠在本機(jī)內(nèi)部達(dá)到最優(yōu)效果。
*高可用性:DBus是基于消息機(jī)制而不是字節(jié)流機(jī)制。它能自動管理一大堆困難的IPC問題。同樣的,DBus庫被設(shè)計來讓程序員能夠使用他們已經(jīng)寫好的代碼。而不會讓他們放棄已經(jīng)寫好的代碼,被迫通過學(xué)習(xí)新的IPC機(jī)制來根據(jù)新的IPC特性重寫這些代碼。
在介紹基本概念之前,先介紹一個學(xué)習(xí)DBus的好工具d-feet,這個工具主要是用來查看DBus的System Bus和Session Bus中的各個消息連接的。當(dāng)然,你也可以在這里面添加自己創(chuàng)建的消息總線,以便于觀察。
下面根據(jù)上圖介紹一下DBus中的一些基本概念。
會話總線(Session Buses)普通進(jìn)程創(chuàng)建,可同時存在多條。會話總線屬于某個進(jìn)程私有,它用于進(jìn)程間傳遞消息。
系統(tǒng)總線(System Bus)在引導(dǎo)時就會啟動,它由操作系統(tǒng)和后臺進(jìn)程使用,安全性非常好,以使得任意的應(yīng)用程序不能欺騙系統(tǒng)事件。當(dāng)然,如果一個應(yīng)用程序需要接受來自系統(tǒng)總線的消息,他也可以直接連接到系統(tǒng)總線中,但是他能發(fā)送的消息是受限的。
Bus Name按字面理解為總線名稱貌似不是很貼切,應(yīng)該是一個連接名稱,主要是用來標(biāo)識一個應(yīng)用和消息總線的連接。從上圖可以看出來,總線名稱主要分為兩類
"org.kde.StatusNotifierWatcher"這種形式的稱為公共名(well-knownname)
":1.3"這種形式的稱為唯一名(Unique Name)
公共名提供眾所周知的服務(wù)。其他應(yīng)用通過這個名稱來使用名稱對應(yīng)的服務(wù)。可能有多個連接要求提供同個公共名的服 務(wù),即多個應(yīng)用連接到消息總線,要求提供同個公共名的服務(wù)。消息總線會把這些連接排在鏈表中,并選擇一個連接提供公共名代表的服務(wù)。可以說這個提供服務(wù)的 連接擁有了這個公共名。如果這個連接退出了,消息總線會從鏈表中選擇下一個連接提供服務(wù)。
唯一名以“:”開頭,“:”后面通常是圓點分隔的兩個數(shù)字,例如“:1.0”。每個連接都有一個唯一名。在一個
消息總線的生命期內(nèi),不會有兩個連接有相同的唯一名。擁有公眾名的連接同樣有唯一名,例如在前面的圖
中,“org.kde.StatusNotifierWatcher”的唯一名是“:1.51”。
每個連接都有一個唯一名,但不一定有公共名。
只有唯一名而沒有公共名叫做私有連接,因為它們沒有提供可以通過公共名訪問的服務(wù)。
Object Paths
“org.kde.StatusNotifierWatcher”這個連接中有三個Object Paths,標(biāo)識這個連接中提供了三個不同的服務(wù),每個Object Paths表示一個服務(wù)。這個路徑在連接中是唯一的。
Interfaces
在每個Object Paths下都包含有多個接口(Interfaces),舉例如下接口:
org.freedesktop.DBus.Introspectable
org.freedesktop.DBus.Properties
org.kde.StatusNotifierWatcher
紅色的兩個是消息總線提供的標(biāo)準(zhǔn)接口,而剩下的一個是需要具體的應(yīng)用去實現(xiàn)的。
Methods和Signals
Methods表示可以被具體調(diào)用的方法
Signals則表示的是信號,此信號可以被廣播,而連接了這個信號的對象在接收到信號時就可以進(jìn)行相應(yīng)的處理。和Qt中的信號應(yīng)該是一個意思。
D-BUS 基礎(chǔ)
dbus的目的主要是下面兩點:
1.在同一個桌面會話中,進(jìn)行桌面應(yīng)用程序之間的通訊
2.桌面程序與內(nèi)核或者守護(hù)進(jìn)程的通信。
dbus中的消息由一個消息頭(標(biāo)識是哪一種消息)和消息數(shù)據(jù)組成,比socket的流式數(shù)據(jù)更方便一些。bus daemon
就像是一個路由器,與各個應(yīng)用程序進(jìn)行連接,分派這些消息。bus daemon
在一臺機(jī)器上有多個實例,第一個實例是全局的實例,類似于sendmail和或者apache,這個實例有很嚴(yán)格的安全限制,只接受一些特定的系統(tǒng)消息,
用于系統(tǒng)通信。其他bus daemon是一些會話,用于用戶登錄之后,在當(dāng)前會話(session)中進(jìn)行的通訊。系統(tǒng)的bus daemon
和會話的bus daemon 是分開的,彼此不會互相影響,會話bus daemon 不會去調(diào)用系統(tǒng)的bus daemon 。
Native Objects and Object Paths
在不同的編程語言中,都定義了一些“對象”,如java中的 java.lang.Object,GLIB中的GObject,QT中的QObject等 等。D-BUS的底層接口,和libdbus API相關(guān),是沒有這些對象的概念的,它提供的是一種叫對象路徑(object path),用于讓高層接口綁定到各個對象中去,允許遠(yuǎn)端應(yīng)用程序指向它們。object path就像是一個文件路徑,可以叫做/org/kde/kspread/sheets/3/cells/4/5等。
Methods and Signals
每個對象都有一些成員,兩種成員:方法(methods)和信號(signals),在對象中,方 法可以被調(diào)用。信號會被廣播,感興趣的對象可以處理這個 信號,同時信號中也可以帶有相關(guān)的數(shù)據(jù)。每一個方法或者信號都可以用一個名字來命名,如”Frobate” 或者 “OnClicked”。
Interfaces
每個對象都有一個或者多個接口,一個接口就是多個方法和信號的集合。dbus使用簡單的命名空間字符串來表示接口,如org.freedesktop.Introspectable。可以說dbus接口相當(dāng)于C++中的純虛類。
Proxies
代理對象用于模擬在另外的進(jìn)程中的遠(yuǎn)端對象,代理對象像是一個正常的普通對象。d-bus的底層接口必須手動創(chuàng)建方法調(diào)用的 消息,然后發(fā)送,同時必須手動 接受和處理返回的消息。高層接口可以使用代理來替換這些,當(dāng)調(diào)用代理對象的方法時,代理內(nèi)部會轉(zhuǎn)換成dbus的方法調(diào)用,等待消息返回,對返回結(jié)果解包, 返回給相應(yīng)的方法??梢钥纯聪旅娴睦?,使用dbus底層接口編寫的代碼:
Message message = new Message("/remote/object/path", "MethodName", arg1, arg2);
Connection connection = getBusConnection();
connection.send(message);
Message reply = connection.waitForReply(message);
if (reply.isError()) {
} else {
Object returnValue = reply.getReturnValue();
}
使用代理對象編寫的代碼:
Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path");
Object returnValue = proxy.MethodName(arg1, arg2);
客戶端代碼減少很多。
Bus Names
當(dāng)一個應(yīng)用程序連接上bus
daemon時,daemon會分配一個唯一的名字給它。以冒號(:)開始,這些名字在daemon的生命周期中是不會改變的,可以認(rèn)為這些名字就是一個
IP地址。當(dāng)這個名字映射到應(yīng)用程序的連接上時,應(yīng)用程序可以說擁有這個名字。同時應(yīng)用可以聲明額外的容易理解的名字,比如可以取一個名字
com.mycompany.TextEditor,可以認(rèn)為這些名字就是一個域名。其他應(yīng)用程序可以往這個名字發(fā)送消息,執(zhí)行各種方法。
名字還有第二個重要的用途,可以用于跟蹤應(yīng)用程序的生命周期。當(dāng)應(yīng)用退出(或者崩潰)時,與bus的連接將被OS內(nèi)核關(guān)掉,bus將會發(fā)送通知,告訴剩余的應(yīng)用程序,該程序已經(jīng)丟失了它的名字。名字還可以檢測應(yīng)用是否已經(jīng)啟動,這往往用于只能啟動一個實例的應(yīng)用。
Addresses
使用d-bus的應(yīng)用程序既可以是server也可以是client,server監(jiān)聽到來的連接,client連接到 server,一旦連接建立,消息 就可以流轉(zhuǎn)。如果使用dbus daemon,所有的應(yīng)用程序都是client,daemon監(jiān)聽所有的連接,應(yīng)用程序初始化連接到daemon。
dbus地址指明server將要監(jiān)聽的地方,client將要連接的地方,例如,地址:unix:path=/tmp/abcdef表明 server將在/tmp/abcdef路徑下監(jiān)聽unix域的socket,client也將連接到這個socket。一個地址也可以指明是 TCP/IP的socket,或者是其他的。
當(dāng)使用bus
daemon時,libdbus會從環(huán)境變量中(DBUS_SESSION_BUS_ADDRESS)自動認(rèn)識“會話daemon”的地址。如果是系統(tǒng)
daemon,它會檢查指定的socket路徑獲得地址,也可以使用環(huán)境變量(DBUS_SESSION_BUS_ADDRESS)進(jìn)行設(shè)定。
當(dāng)dbus中不使用daemon時,需要定義哪一個應(yīng)用是server,哪一個應(yīng)用是client,同時要指明server的地址,這不是很通常的做法。
Big Conceptual Picture
要在指定的對象中調(diào)用指定的方法,需要知道的參數(shù)如下:
Address -> [Bus Name] -> Path -> Interface -> Method
bus name是可選的,除非是希望把消息送到特定的應(yīng)用中才需要。interface也是可選的,有一些歷史原因,DCOP不需要指定接口,因為DCOP在同一個對象中禁止同名的方法。
Messages - Behind the Scenes
如果使用dbus的高層接口,就可以不用直接操作這些消息。DBUS有四種類型的消息:
1.方法調(diào)用(method call) 在對象上執(zhí)行一個方法
2.方法返回(method return)返回方法執(zhí)行的結(jié)果
3.錯誤(error)調(diào)用方法產(chǎn)生的異常
4.信號(signal)通知指定的信號發(fā)生了,可以想象成“事件”。
要執(zhí)行 D-BUS 對象的方法,需要向?qū)ο蟀l(fā)送一個方法調(diào)用消息。它將完成一些處理并返回一個方法返回消息或者錯誤消息。信號的不同之處在于它們不返回任何內(nèi)容:既沒有“信號返回”消息,也沒有任何類型的錯誤消息。
每個消息都有一個消息頭,包含多個字段,有一個消息體,包含多個參數(shù)。可以認(rèn)為消息頭是消息的路由信息,消息體作為一個載體。消息頭里面的字段包含
發(fā)送的bus name,目標(biāo)bus
name,方法或者信號名字等,同時消息頭里面定義的字段類型規(guī)定了消息體里面的數(shù)據(jù)格式。例如:字符“i”代表了”32-bit
integer”,“ii”就代表了消息體里面有兩個”32-bit integer”。
Calling a Method - Behind the Scenes
在dbus中調(diào)用一個方法包含了兩條消息,進(jìn)程A向進(jìn)程B發(fā)送 方法調(diào)用消息,進(jìn)程B向進(jìn)程A發(fā)送應(yīng)答消息。所有的消息都由daemon進(jìn)行分派,每個調(diào)用 的消息都有一個不同的序列號,返回消息包含這個序列號,以方便調(diào)用者匹配調(diào)用消息與應(yīng)答消息。調(diào)用消息包含一些參數(shù),應(yīng)答消息可能包含錯誤標(biāo)識,或者包含 方法的返回數(shù)據(jù)。
方法調(diào)用的一般流程:
1.使用不同語言綁定的dbus高層接口,都提供了一些代理對象,調(diào)用其他進(jìn)程里面的遠(yuǎn)端對象就像是在本地進(jìn)程中的調(diào)用一樣。應(yīng)用調(diào)用代理上的方法,代理將構(gòu)造一個方法調(diào)用消息給遠(yuǎn)端的進(jìn)程。
2.在DBUS的底層接口中,應(yīng)用需要自己構(gòu)造方法調(diào)用消息(method call message),而不能使用代理。
3.方法調(diào)用消息里面的內(nèi)容有:目的進(jìn)程的bus name,方法的名字,方法的參數(shù),目的進(jìn)程的對象路徑,以及可選的接口名稱。
4.方法調(diào)用消息是發(fā)送到bus daemon中的。
5.bus daemon查找目標(biāo)的bus name,如果找到,就把這個方法發(fā)送到該進(jìn)程中,否則,daemon會產(chǎn)生錯誤消息,作為應(yīng)答消息給發(fā)送進(jìn)程。
6.
目標(biāo)進(jìn)程解開消息,在dbus底層接口中,會立即調(diào)用方法,然后發(fā)送方法的應(yīng)答消息給daemon。在dbus高層接口中,會先檢測對象路徑,接口,
方法名稱,然后把它轉(zhuǎn)換成對應(yīng)的對象(如GObject,QT中的QObject等)的方法,然后再將應(yīng)答結(jié)果轉(zhuǎn)換成應(yīng)答消息發(fā)給daemon。
7.bus daemon接受到應(yīng)答消息,將把應(yīng)答消息直接發(fā)給發(fā)出調(diào)用消息的進(jìn)程。
8.應(yīng)答消息中可以包容很多返回值,也可以標(biāo)識一個錯誤發(fā)生,當(dāng)使用綁定時,應(yīng)答消息將轉(zhuǎn)換為代理對象的返回值,或者進(jìn)入異常。
bus daemon不對消息重新排序,如果發(fā)送了兩條消息到同一個進(jìn)程,他們將按照發(fā)送順序接受到。接受進(jìn)程需要按照順序發(fā)出應(yīng)答消息,例如在多線程中處理這些消息,應(yīng)答消息的發(fā)出是沒有順序的。消息都有一個序列號可以與應(yīng)答消息進(jìn)行配對。
Emitting a Signal - Behind the Scenes
在dbus中一個信號包含一條信號消息,一個進(jìn)程發(fā)給多個進(jìn)程。也就是說,信號是單向的廣播。信號可以包含一些參數(shù),但是作為廣播,它是沒有返回值的。
信號觸發(fā)者是不了解信號接受者的,接受者向daemon注冊感興趣的信號,注冊規(guī)則是”match rules”,記錄觸發(fā)者名字和信號名字。daemon只向注冊了這個信號的進(jìn)程發(fā)送信號。
信號的一般流程如下:
1.當(dāng)使用dbus底層接口時,信號需要應(yīng)用自己創(chuàng)建和發(fā)送到daemon,使用dbus高層接口時,可以使用相關(guān)對象進(jìn)行發(fā)送,如Glib里面提供的信號觸發(fā)機(jī)制。
2.信號包含的內(nèi)容有:信號的接口名稱,信號名稱,發(fā)送進(jìn)程的bus name,以及其他參數(shù)。
3.任何進(jìn)程都可以依據(jù)”match rules”注冊相關(guān)的信號,daemon有一張注冊的列表。
4.daemon檢測信號,決定哪些進(jìn)程對這個信號感興趣,然后把信號發(fā)送給這些進(jìn)程。
5.每個進(jìn)程收到信號后,如果是使用了dbus高層接口,可以選擇觸發(fā)代理對象上的信號。如果是dbus底層接口,需要檢查發(fā)送者名稱和信號名稱,然后決定怎么做。
1. 進(jìn)程間使用D-Bus通信
D-Bus是一種高級的進(jìn)程間通信機(jī)制,它由freedesktop.org項目提供,使用GPL許可證發(fā)行。D-Bus最主要的用途是在
Linux桌面環(huán)境為進(jìn)程提供通信,同時能將Linux桌面環(huán)境和Linux內(nèi)核事件作為消息傳遞到進(jìn)程。D-Bus的主要概率為總線,注冊后的進(jìn)程可通
過總線接收或傳遞消息,進(jìn)程也可注冊后等待內(nèi)核事件響應(yīng),例如等待網(wǎng)絡(luò)狀態(tài)的轉(zhuǎn)變或者計算機(jī)發(fā)出關(guān)機(jī)指令。目前,D-Bus已被大多數(shù)Linux發(fā)行版所
采用,開發(fā)者可使用D-Bus實現(xiàn)各種復(fù)雜的進(jìn)程間通信任務(wù)。
2. D-Bus的基本概念
D-Bus是一個消息總線系統(tǒng),其功能已涵蓋進(jìn)程間通信的所有需求,并具備一些特殊的用途。D-Bus是三層架構(gòu)的進(jìn)程間通信系統(tǒng),其中包括:
接口層:接口層由函數(shù)庫libdbus提供,進(jìn)程可通過該庫使用D-Bus的能力。
總線層:總線層實際上是由D-Bus總線守護(hù)進(jìn)程提供的。它在Linux系統(tǒng)啟動時運行,負(fù)責(zé)進(jìn)程間的消息路由和傳遞,其中包括Linux內(nèi)核和Linux桌面環(huán)境的消息傳遞。
包裝層:包裝層一系列基于特定應(yīng)用程序框架的Wrapper庫。
D-Bus具備自身的協(xié)議,協(xié)議基于二進(jìn)制數(shù)據(jù)設(shè)計,與數(shù)據(jù)結(jié)構(gòu)和編碼方式無關(guān)。該協(xié)議無需對數(shù)據(jù)進(jìn)行序列化,保證了信息傳遞的高效性。無論是libdbus,還是D-Bus總線守護(hù)進(jìn)程,均不需要太大的系統(tǒng)開銷。
總線是D-Bus的進(jìn)程間通信機(jī)制,一個系統(tǒng)中通常存在多條總線,這些總線由D-Bus總線守護(hù)進(jìn)程管理。最重要的總線為系統(tǒng)總線(System
Bus),Linux內(nèi)核引導(dǎo)時,該總線就已被裝入內(nèi)存。只有Linux內(nèi)核、Linux桌面環(huán)境和權(quán)限較高的程序才能向該總線寫入消息,以此保障系統(tǒng)安
全性,防止有惡意進(jìn)程假冒Linux發(fā)送消息。
會話總線(Session Buses)由普通進(jìn)程創(chuàng)建,可同時存在多條。會話總線屬于某個進(jìn)程私有,它用于進(jìn)程間傳遞消息。
進(jìn)程必須注冊后才能收到總線中的消息,并且可同時連接到多條總線中。D-Bus提供了匹配器(Matchers)使進(jìn)程可以有選擇性的接收消息,另
外運行進(jìn)程注冊回調(diào)函數(shù),在收到指定消息時進(jìn)行處理。匹配器的功能等同與路由,用于避免處理無關(guān)消息造成進(jìn)程的性能下降。除此以外,D-Bus機(jī)制的重要
概念有以下幾個。
對象:對象是封裝后的匹配器與回調(diào)函數(shù),它以對等(peer-to-peer)協(xié)議使每個消息都有一個源地址和一個目的地址。這些地址又稱為對象路
徑,或者稱之為總線名稱。對象的接口是回調(diào)函數(shù),它以類似C++的虛擬函數(shù)實現(xiàn)。當(dāng)一個進(jìn)程注冊到某個總線時,都要創(chuàng)建相應(yīng)的消息對象。
消息:D-Bus的消息分為信號(signals)、方法調(diào)用(method calls)、方法返回(method
returns)和錯誤(errors)。信號是最基本的消息,注冊的進(jìn)程可簡單地發(fā)送信號到總線上,其他進(jìn)程通過總線讀取消息。方法調(diào)用是通過總線傳遞
參數(shù),執(zhí)行另一個進(jìn)程接口函數(shù)的機(jī)制,用于某個進(jìn)程控制另一個進(jìn)程。方法返回是注冊的進(jìn)程在收到相關(guān)信息后,自動做出反應(yīng)的機(jī)制,由回調(diào)函數(shù)實現(xiàn)。錯誤是
信號的一種,是注冊進(jìn)程錯誤處理機(jī)制之一。
服務(wù):服務(wù)(Services)是進(jìn)程注冊的抽象。進(jìn)程注冊某個地址后,即可獲得對應(yīng)總線的服務(wù)。D-Bus提供了服務(wù)查詢接口,進(jìn)程可通過該接口查詢某個服務(wù)是否存在?;蛘咴诜?wù)結(jié)束時自動收到來自系統(tǒng)的消息。
建立服務(wù)的流程:
----------------------------------
建立一個dbus連接之后 -- dbus_bus_get(),為這個dbus連接(DbusConnection)起名 --
dbus_bus_request_name(),這個名字將會成為我們在后續(xù)進(jìn)行遠(yuǎn)程調(diào)用的時候的服務(wù)名,然后我們進(jìn)入監(jiān)聽循環(huán) --
dbus_connection_read_write()。在循環(huán)中,我們從總線上取出消息 --
dbus_connection_pop_message(),并通過比對消息中的方法接口名和方法名 --
dbus_message_is_method_call(),如果一致,那么我們跳轉(zhuǎn)到相應(yīng)的處理中去。在相應(yīng)的處理中,我們會從消息中取出遠(yuǎn)程調(diào)用的
參數(shù)。并且建立起回傳結(jié)果的通路 -- reply_to_method_call()?;貍鲃幼鞅旧淼韧谝淮尾恍枰却Y(jié)果的遠(yuǎn)程調(diào)用。
發(fā)送信號的流程:
----------------------------------
建立一個dbus連接之后,為這個dbus連接起名,建立一個發(fā)送信號的通道,注意,在建立通道的函數(shù)中,需要我們填寫該信號的接口名和信號名
-- dbus_message_new_signal()。然后我們把信號對應(yīng)的相關(guān)參數(shù)壓進(jìn)去 --
dbus_message_iter_init_append();
dbus_message_iter_append_basic()。然后就可以啟動發(fā)送了 -- dbus_connection_send();
dbus_connection_flush。
進(jìn)行一次遠(yuǎn)程調(diào)用的流程:
----------------------------------
建立好dbus連接之后,為這dbus連接命名,申請一個遠(yuǎn)程調(diào)用通道 --
dbus_message_new_method_call(),注意,在申請遠(yuǎn)程調(diào)用通道的時候,需要填寫服務(wù)器名,本次調(diào)用的接口名,和本次調(diào)用名
(方法名)。壓入本次調(diào)用的參數(shù) -- dbus_message_iter_init_append();
dbus_message_iter_append_basic(),實際上是申請了一個首地址,我們就是把我們真正要傳的參數(shù),往這個首地址里面送(送
完之后一般都會判斷是否內(nèi)存越界了)。然后就是啟動發(fā)送調(diào)用并釋放發(fā)送相關(guān)的消息結(jié)構(gòu) --
dbus_connection_send_with_reply()。這個啟動函數(shù)中帶有一個句柄。我們馬上會阻塞等待這個句柄給我們帶回總線上回傳的
消息。當(dāng)這個句柄回傳消息之后,我們從消息結(jié)構(gòu)中分離出參數(shù)。用dbus提供的函數(shù)提取參數(shù)的類型和參數(shù) --
dbus_message_iter_init(); dbus_message_iter_next();
dbus_message_iter_get_arg_type();
dbus_message_iter_get_basic()。也就達(dá)成了我們進(jìn)行本次遠(yuǎn)程調(diào)用的目的了。
信號接收流程:
----------------------------------
建立一個dbus連接之后,為這個dbus連接起名,為我們將要進(jìn)行的消息循環(huán)添加匹配條件(就是通過信號名和信號接口名來進(jìn)行匹配控制的)
--
dbus_bus_add_match()。我們進(jìn)入等待循環(huán)后,只需要對信號名,信號接口名進(jìn)行判斷就可以分別處理各種信號了。在各個處理分支上。我們
可以分離出消息中的參數(shù)。對參數(shù)類型進(jìn)行判斷和其他的處理。
dbus_connection_read_write()
--------------------------------------
As long as the connection is open, this function will block until
it can read or write, then read or write, then return #TRUE.
If the connection is closed, the function returns #FALSE.
dbus_connection_pop_message()
--------------------------------------
Returns the first-received message from the incoming message queue,
removing it from the queue. The caller owns a reference to the returned
message. If the queue is empty, returns #NULL.
dbus_connection_send()
--------------------------------------
Adds a message to the outgoing message queue. Does not block to
write the message to the network; that happens asynchronously. To force
the message to be written, call dbus_connection_flush(). Because this
only queues the message, the only reason it can
fail is lack of memory. Even if the connection is disconnected, no error will be returned.
@param connection the connection.
@param message the message to write.
@param serial return location for message serial, or #NULL if you don't care
@returns #TRUE on success.
dbus_connection_send_with_reply()
--------------------------------------
Queues a message to send, as with dbus_connection_send(), but also
returns a #DBusPendingCall used to receive a reply to the message. If no
reply is received in the given timeout_milliseconds, this function
expires the pending reply and generates a synthetic error reply
(generated in-process, not by the remote application) indicating that a
timeout occurred.
A #DBusPendingCall will see a reply message
before any filters or registered object path handlers. See
dbus_connection_dispatch() for details on when handlers are run.
A #DBusPendingCall will always see exactly one reply message, unless it's cancelled with dbus_pending_call_cancel().
If #NULL is passed for the pending_return, the #DBusPendingCall
will still be generated internally, and used to track the message reply
timeout. This means a timeout error will occur if no reply arrives,
unlike with dbus_connection_send().
If -1 is passed for the
timeout, a sane default timeout is used. -1 is typically the best value
for the timeout for this reason, unless you want a very short or very
long timeout. There is no way to avoid a timeout entirely, other than
passing INT_MAX for the
timeout to mean "very long timeout." libdbus clamps an INT_MAX timeout down to a few hours timeout though.
@warning if the connection is disconnected, the #DBusPendingCall will be set to #NULL, so be careful with this.
@param connection the connection
@param message the message to send
@param pending_return return location for a #DBusPendingCall object, or #NULL if connection is disconnected
@param timeout_milliseconds timeout in milliseconds or -1 for default
@returns #FALSE if no memory, #TRUE otherwise.
dbus_message_is_signal()
--------------------------------------
Checks whether the message is a signal with the given interface and
member fields. If the message is not #DBUS_MESSAGE_TYPE_SIGNAL, or has a
different interface or member field, returns #FALSE.
dbus_message_iter_init()
--------------------------------------
Initializes a #DBusMessageIter for reading the arguments of the message passed in.
dbus_message_iter_next()
--------------------------------------
Moves the iterator to the next field, if any. If there's no next
field, returns #FALSE. If the iterator moves forward, returns #TRUE.
dbus_message_iter_get_arg_type()
--------------------------------------
Returns the argument type of the argument that the message iterator
points to. If the iterator is at the end of the message, returns
#DBUS_TYPE_INVALID.
dbus_message_iter_get_basic()
--------------------------------------
Reads a basic-typed value from the message iterator. Basic types are the non-containers such as integer and string.
dbus_message_new_signal()
--------------------------------------
Constructs a new message representing a signal emission. Returns
#NULL if memory can't be allocated for the message. A signal is
identified by its originating object path, interface, and the name of
the signal.
Path, interface, and signal name must all be valid (the D-Bus specification defines the syntax of these fields).
@param path the path to the object emitting the signal
@param interface the interface the signal is emitted from
@param name name of the signal
@returns a new DBusMessage, free with dbus_message_unref()
dbus_message_iter_init_append()
--------------------------------------
Initializes a #DBusMessageIter for appending arguments to the end of a message.
@param message the message
@param iter pointer to an iterator to initialize
dbus_message_iter_append_basic()
--------------------------------------
Appends a basic-typed value to the message. The basic types are the non-container types such as integer and string.
@param iter the append iterator
@param type the type of the value
@param value the address of the value
@returns #FALSE if not enough memory
dbus_message_new_method_call()
--------------------------------------
Constructs a new message to invoke a method on a remote object.
Returns #NULL if memory can't be allocated for the message. The
destination may be #NULL in which case no destination is set; this is
appropriate when using D-Bus in a peer-to-peer context (no message bus).
The interface may be #NULL, which means that if multiple methods with
the given name exist it is undefined which one will be invoked.
The path and method names may not be #NULL.
Destination, path, interface, and method name can't contain any invalid characters (see the D-Bus specification).
@param destination name that the message should be sent to or #NULL
@param path object path the message should be sent to
@param interface interface to invoke method on, or #NULL
@param method method to invoke
@returns a new DBusMessage, free with dbus_message_unref()
dbus_bus_get()
--------------------------------------
Connects to a bus daemon and registers the client with it. If a
connection to the bus already exists, then that connection is returned.
The caller of this function owns a reference to the bus.
@param type bus type
@param error address where an error can be returned.
@returns a #DBusConnection with new ref
dbus_bus_request_name()
--------------------------------------
Asks the bus to assign the given name to this connection by invoking the RequestName method on the bus.
First you should know that for each bus name, the bus stores a
queue of connections that would like to own it. Only one owns it at a
time - called the primary owner. If the primary owner releases the name
or disconnects, then the next owner in the queue atomically takes over.
So for example if you have an application
org.freedesktop.TextEditor and multiple instances of it can be run, you
can have all of them sitting in the queue. The first one to start up
will receive messages sent to org.freedesktop.TextEditor, but if that
one exits another will become the primary owner and receive messages.
The queue means you don't need to manually watch for the current owner to disappear and then request the name again.
@param connection the connection
@param name the name to request
@param flags flags
@param error location to store the error
@returns a result code, -1 if error is set
給DBusConnection起名字(命名) -- 兩個相互通信的連接(connection)不能同名
命名規(guī)則: xxx.xxx (zeng.xiaolong)
dbus_bus_add_match()
--------------------------------------
Adds a match rule to match messages going through the message bus. The "rule" argument is the string form of a match rule.
@param connection connection to the message bus
@param rule textual form of match rule
@param error location to store any errors
dbus_pending_call_block()
--------------------------------------
Block until the pending call is completed. The blocking is as with
dbus_connection_send_with_reply_and_block(); it does not enter the main
loop or process other messages, it simply waits for the reply in
question.
If the pending call is already completed, this function returns immediately.
@todo when you start blocking, the timeout is reset, but it should
really only use time remaining since the pending call was created. This
requires storing timestamps instead of intervals in the timeout
@param pending the pending call
dbus_pending_call_steal_reply()
--------------------------------------
Gets the reply, or returns #NULL if none has been received yet.
Ownership of the reply message passes to the caller. This function can
only be called once per pending call, since the reply message is
tranferred to the caller.
@param pending the pending call
@returns the reply message or #NULL.
安裝D-Bus可在其官方網(wǎng)站下載源碼編譯,地址為http://dbus.freedesktop.org?;蛘咴诮K端上輸入下列指令:
yum install dbus dbus-devel dbus-doc
安裝后,頭文件位于"/usr/include/dbus-<版本號>/dbus"目錄中,編譯使用D-Bus的程序時需加入編譯指令"`pkg-config --cflags --libs dbus-1`"。
3. D-Bus的用例
在使用GNOME桌面環(huán)境的Linux系統(tǒng)中,通常用GLib庫提供的函數(shù)來管理總線。在測試下列用例前,首先需要安裝GTK+開發(fā)包(見22.3節(jié))并配置編譯環(huán)境。該用例一共包含兩個程序文件,每個程序文件需單獨編譯成為可執(zhí)行文件。
1.消息發(fā)送程序
"dbus-ding-send.c"程序每秒通過會話總線發(fā)送一個參數(shù)為字符串Ding!的信號。該程序的源代碼如下:
#include <glib.h> // 包含glib庫
#include <dbus/dbus-glib.h> // 包含
glib庫中D-Bus管理庫
#include <stdio.h>
static gboolean send_ding(DBusConnection *bus);// 定義發(fā)送消息函數(shù)的原型
int main ()
{
GMainLoop *loop; // 定義一個事件循環(huán)對象的指針
DBusConnection *bus; // 定義總線連接對象的指針
DBusError error; // 定義D-Bus錯誤消息對象
loop = g_main_loop_new(NULL, FALSE); // 創(chuàng)建新事件循環(huán)對象
dbus_error_init (&error); // 將錯誤消息對象連接到D-Bus
// 錯誤消息對象
bus = dbus_bus_get(DBUS_BUS_SESSION, &error);// 連接到總線
if (!bus) { // 判斷是否連接錯誤
g_warning("連接到D-Bus失敗: %s", error.message);
// 使用GLib輸出錯誤警告信息
dbus_error_free(&error); // 清除錯誤消息
return 1;
}
dbus_connection_setup_with_g_main(bus, NULL);
// 將總線設(shè)為接收GLib事件循環(huán)
g_timeout_add(1000, (GSourceFunc)send_ding, bus);
// 每隔1000ms調(diào)用一次send_ding()函數(shù)
// 將總線指針作為參數(shù)
g_main_loop_run(loop); // 啟動事件循環(huán)
return 0;
}
static gboolean send_ding(DBusConnection *bus) // 定義發(fā)
送消息函數(shù)的細(xì)節(jié)
{
DBusMessage *message; // 創(chuàng)建消息對象指針
message = dbus_message_new_signal("/com/burtonini/dbus/ding",
"com.burtonini.dbus.Signal",
"ding"); // 創(chuàng)建消息對象并標(biāo)識路徑
dbus_message_append_args(message,
DBUS_TYPE_STRING, "ding!",
DBUS_TYPE_INVALID); //將字符串Ding!定義為消息
dbus_connection_send(bus, message, NULL); // 發(fā)送該消息
dbus_message_unref(message); // 釋放消息對象
g_print("ding!/n"); // 該函數(shù)等同與標(biāo)準(zhǔn)輸入輸出
return TRUE;
}
main()函數(shù)創(chuàng)建一個GLib事件循環(huán),獲得會話總線的一個連接,并將D-Bus事件處理集成到GLib事件循環(huán)之中。然后它創(chuàng)建了一個名為
send_ding()函數(shù)作為間隔為一秒的計時器,并啟動事件循環(huán)。send_ding()函數(shù)構(gòu)造一個來自于對象路徑"/com/burtonini
/dbus/ding"和接口"com.burtonini.dbus.Signal"的新的Ding信號。然后,字符串Ding!作為參數(shù)添加到信號中
并通過總線發(fā)送。在標(biāo)準(zhǔn)輸出中會打印一條消息以讓用戶知道發(fā)送了一個信號。
2.消息接收程序
dbus-ding-listen.c程序通過會話總線接收dbus-ding-send.c程序發(fā)送到消息。該程序的源代碼如下:
#include <glib.h> // 包含glib庫
#include <dbus/dbus-glib.h> // 包含glib庫中D-Bus管理庫
static DBusHandlerResult signal_filter // 定義接收消息函數(shù)的原型
(DBusConnection *connection, DBusMessage *message, void *user_data);
int main()
{
GMainLoop *loop; // 定義一個事件循環(huán)對象的指針
DBusConnection *bus; // 定義總線連接對象的指針
DBusError error; // 定義D-Bus錯誤消息對象
loop = g_main_loop_new(NULL, FALSE); // 創(chuàng)建新事件循環(huán)對象
dbus_error_init(&error); // 將錯誤消息對象連接到D-Bus
// 錯誤消息對象
bus = dbus_bus_get(DBUS_BUS_SESSION, &error); // 連接到總線
if (!bus) { // 判斷是否連接錯誤
g_warning("連接到D-Bus失敗: %s", error.message);
// 使用GLib輸出錯誤警告信息
dbus_error_free(&error); // 清除錯誤消息
return 1;
}
dbus_connection_setup_with_g_main(bus, NULL);
// 將總線設(shè)為接收GLib事件循環(huán)
dbus_bus_add_match(bus, "type='signal',interface
='com.burtonini.dbus.Signal'"); // 定義匹配器
dbus_connection_add_filter(bus, signal_filter, loop, NULL);
// 調(diào)用函數(shù)接收消息
g_main_loop_run(loop); // 啟動事件循環(huán)
return 0;
}
static DBusHandlerResult // 定義接收消息函數(shù)的細(xì)節(jié)
signal_filter (DBusConnection *connection,
DBusMessage *message, void *user_data)
{
GMainLoop *loop = user_data; // 定義事件循環(huán)對象的指針,并與主函數(shù)中的同步
if (dbus_message_is_signal // 接收連接成功消息,判斷是否連接失敗
(message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL,
"Disconnected")) {
g_main_loop_quit (loop); // 退出主循環(huán)
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus_message_is_signal(message, "com.burtonini.dbus.Signal",
"Ping")) {
// 指定消息對象路徑,判斷是否成功
DBusError error; // 定義錯誤對象
char *s;
dbus_error_init(&error); // 將錯誤消息對象連接到D-Bus錯誤
// 消息對象
if (dbus_message_get_args // 接收消息,并判斷是否有錯誤
(message, &error, DBUS_TYPE_STRING, &s,
DBUS_TYPE_INVALID)) {
g_print("接收到的消息是: %s/n", s); // 輸出接收到的消息
dbus_free (s); // 清除該消息
}
else { // 有錯誤時執(zhí)行下列語句
g_print("消息已收到,但有錯誤提示: %s/n", error.message);
dbus_error_free (&error);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
該程序偵聽dbus-ping-send.c程序正在發(fā)出的信號。main()函數(shù)和前面一樣啟動,創(chuàng)建一個到總線的連接。然后它聲明愿意在使用
com.burtonini.dbus.Signal接口的信號被發(fā)送時得到通知,將signal_filter()函數(shù)設(shè)置為通知函數(shù),然后進(jìn)入事件循
環(huán)。當(dāng)滿足匹配的消息被發(fā)送時,signal_func()函數(shù)會被調(diào)用。
如果需要確定在接收消息時如何處理,可通過檢測消息頭實現(xiàn)。若收到的消息為總線斷開信號,則主事件循環(huán)將被終止,因為監(jiān)聽的總線已經(jīng)不存在了。若收
到其他的消息,首先將收到的消息與期待的消息進(jìn)行比較,兩者相同則輸出其中參數(shù),并退出程序。兩者不相同則告知總線并沒有處理該消息,這樣消息會繼續(xù)保留
在總線中供別的程序處理。
dbus實例講解
http://blog.csdn.net/fmddlmyy/archive/2008/12/23/3585730.aspx
網(wǎng)上有不少介紹dbus的文章。本文的目標(biāo)是補(bǔ)充一些簡單的例子。
1、dbus是什么東西?
網(wǎng)上有一篇叫“D-Bus Tutorial”的文章,流傳較廣。不少介紹dbus的資料,都引用了其中的段落。其實相對于這篇文章,我建議大家直接讀“D-Bus Specification”,篇幅不算長,文字也不算枯燥。
D-Bus是針對桌面環(huán)境優(yōu)化的IPC(interprocess communication
)機(jī)制,用于進(jìn)程間的通信或進(jìn)程與內(nèi)核的通信。最基本的D-Bus協(xié)議是一對一的通信協(xié)議。但在很多情況下,通信的一方是消息總線。消息總線是一個特殊的
應(yīng)用,它同時與多個應(yīng)用通信,并在應(yīng)用之間傳遞消息。下面我們會在實例中觀察消息總線的作用。消息總線的角色有點類似與X系統(tǒng)中的窗口管理器,窗口管理器
既是X客戶,又負(fù)責(zé)管理窗口。
支持dbus的系統(tǒng)都有兩個標(biāo)準(zhǔn)的消息總線:系統(tǒng)總線和會話總線。系統(tǒng)總線用于系統(tǒng)與應(yīng)用的通信。會話總線用于應(yīng)用之間的通信。網(wǎng)上有一個叫d-feet的python程序,我們可以用它來觀察系統(tǒng)中的dbus世界。
圖1、由d-feet觀察到的D-Bus世界
D-Bus是一個程序。它提供了API。但我們一般不會直接使用dbus的接口。dbus-glib是GTK版本的dbus接口封裝。本文假設(shè)讀者
安裝了dbus-glib,我安裝的是dbus-glib-0.76。后面還會看到,通過python操縱dbus是多么簡單。
2、D-Bus的基本概念
2.1、從例子開始
我寫了一個最簡單的dbus服務(wù)器,它通過dbus提供了一個加法的接口。大家可以下載這個例子。這是一個autotool工程,大家解包后,執(zhí)行:
然后在src目錄運行:
這時再運行d-feet,連接session bus,在“Bus Name”窗口會看到一個叫“org.fmddlmyy.Test”連接名。
圖2、提供D-Bus服務(wù)的org.fmddlmyy.Test
選擇“org.fmddlmyy.Test”,在右側(cè)窗口點擊展開“Object
Paths”->“/TestObj”->“Interfaces”->“org.fmddlmyy.Test.Basic”->“Methods”,
可以看到一個Add方法。雙擊Add方法,彈出下面這個對話框:
圖3、通過D-Bus接口計算1+2=3
在Parameters窗口輸入“1,2”,點擊“Execute”按鈕,然后在“Output”窗口我們看到了輸出結(jié)果。我們剛剛創(chuàng)建了一個dbus服務(wù)并調(diào)用了它。
2.2、名詞
我們來解釋一下d-feet中出現(xiàn)的名詞。
2.2.1、Bus Name
可以把Bus Name理解為連接的名稱,一個Bus Name總是代表一個應(yīng)用和消息總線的連接。有兩種作用不同的Bus Name,一個叫公共名(well-known names),還有一個叫唯一名(Unique Connection Name)。
2.2.1.1、可能有多個備選連接的公共名
公共名提供眾所周知的服務(wù)。其他應(yīng)用通過這個名稱來使用名稱對應(yīng)的服務(wù)。可能有多個連接要求提供同個公共名的服務(wù),即多個應(yīng)用連接到消息總線,要求
提供同個公共名的服務(wù)。消息總線會把這些連接排在鏈表中,并選擇一個連接提供公共名代表的服務(wù)??梢哉f這個提供服務(wù)的連接擁有了這個公共名。如果這個連接
退出了,消息總線會從鏈表中選擇下一個連接提供服務(wù)。公共名是由一些圓點分隔的多個小寫標(biāo)志符組成的,例如“org.fmddlmyy.Test”、
“org.bluez”。
2.2.1.2、每個連接都有一個唯一名
當(dāng)應(yīng)用連接到消息總線時,消息總線會給每個應(yīng)用分配一個唯一名。唯一名以“:”開頭,“:”后面通常是圓點分隔的兩個數(shù)字,例如“:1.0”。每個
連接都有一個唯一名。在一個消息總線的生命期內(nèi),不會有兩個連接有相同的唯一名。擁有公眾名的連接同樣有唯一名,例如在前面的圖
中,“org.fmddlmyy.Test”的唯一名是“:1.17”。
有的連接只有唯一名,沒有公眾名??梢园堰@些名稱稱為私有連接,因為它們沒有提供可以通過公共名訪問的服務(wù)。 d-feet界面上有個“Hide Private”按鈕,可以用來隱藏私有連接。
2.2.2、Object Paths
Bus Name確定了一個應(yīng)用到消息總線的連接。在一個應(yīng)用中可以有多個提供服務(wù)的對象。這些對象按照樹狀結(jié)構(gòu)組織起來。每個對象都有一個唯一的路徑(Object Paths)?;蛘哒f,在一個應(yīng)用中,一個對象路徑標(biāo)志著一個唯一的對象。
“org.fmddlmyy.Test”只有一個叫作“/TestObj”的對象。圖1中的“org.bluez”有多個對象路徑。
2.2.3、Interfaces
通過對象路徑,我們找到應(yīng)用中的一個對象。每個對象可以實現(xiàn)多個接口。例如:“org.fmddlmyy.Test”的“/TestObj”實現(xiàn)了以下接口:
后面講代碼時會看到,我們在代碼中其實只實現(xiàn)了“org.fmddlmyy.Test.Basic”這個接口。接口
“org.freedesktop.DBus.Introspectable”和“org.freedesktop.DBus.Properties”是
消息總線提供的標(biāo)準(zhǔn)接口。
2.2.4、Methods和Signals
接口包括方法和信號。例如“org.fmddlmyy.Test”的“/TestObj”對象的“org.fmddlmyy.Test.Basic”接口有一個Add方法。后面的例子中我們會介紹信號。
標(biāo)準(zhǔn)接口“org.freedesktop.DBus.Introspectable”的Introspect方法是個很有用的方法。類似于
Java的反射接口,調(diào)用Introspect方法可以返回接口的xml描述。我們雙擊
“org.fmddlmyy.Test”->“/TestObj”->“org.fmddlmyy.Test.Basic”->“org.freedesktop.DBus.Introspectable”
的Introspect方法。這個方法沒有輸入?yún)?shù),我們直接點擊“Execute”按鈕,你在“Output”窗口看到了什么?
圖4、調(diào)用Introspect方法
后面我們會用另一種方式調(diào)用Introspect方法。
2.3 小結(jié)
“org.fmddlmyy.Test”->“/TestObj”->“org.fmddlmyy.Test.Basic”->“org.freedesktop.DBus.Introspectable”
的Introspect方法,這個描述是不是很麻煩。其實前面還要加上“session bus”。
后面在看客戶端的C代碼時,我們會看到同樣的過程:用dbus_g_bus_get得到到session
bus的連接。在這個連接上用dbus_g_proxy_new_for_name函數(shù)獲得到擁有指定公共名的連接的指定對象的指定接口的代理。最后,用
dbus_g_proxy_call函數(shù)通過接口代理調(diào)用接口提供的方法。
3 下集預(yù)告
d-feet雖然很方便,但它使用了python的gtk模塊,在一些嵌入式環(huán)境可能使用不了。后面會看到,用一個叫dbus-send的命令行工
具,或者寫幾行python腳本都可以完成同樣的工作。我們還會用一個叫dbus-monitor的命令行工具觀察dbus調(diào)用過程中究竟發(fā)生了什么?
應(yīng)用程序A和消息總線連接,這個連接獲取了一個眾所周知的公共名(記作連接A)。應(yīng)用程序A中有對象A1提供了接口I1,接口I1有方法M1。應(yīng)用程序B和消息總線連接,要求調(diào)用連接A上對象A1的接口I1的方法M1。
在上一講的加法例子中,上面這段話可以實例化為:應(yīng)用程序example-service和會話總線連接。這個連接獲取了一個眾所周知的公共名
“org.fmddlmyy.Test”。應(yīng)用程序example-servic中有對象“/TestObj”提供了接口
“org.fmddlmyy.Test.Basic”,接口“org.fmddlmyy.Test.Basic”有方法“Add”。應(yīng)用程序d-feet
和會話總線連接,要求調(diào)用連接“org.fmddlmyy.Test”上對象“/TestObj”的接口
“org.fmddlmyy.Test.Basic”的方法“Add”。
應(yīng)用程序B調(diào)用應(yīng)用程序A的方法,其實就是應(yīng)用程序B向應(yīng)用程序A發(fā)送了一個類型為“method_call”的消息。應(yīng)用程序A通過一個類型為“method_retutn”的消息將返回值發(fā)給應(yīng)用程序B。我們簡單介紹一下D-Bus總線上的消息。
1、D-Bus的消息
上一講說過最基本的D-Bus協(xié)議是一對一的通信協(xié)議。與直接使用socket不同,D-Bus是面向消息的協(xié)議。 D-Bus的所有功能都是通過在連接上流動的消息完成的。
1.1、消息類型
D-Bus有四種類型的消息:
method_call 方法調(diào)用
method_return 方法返回
error 錯誤
signal 信號
前面介紹的遠(yuǎn)程方法調(diào)用就用到了method_call和method_return消息。顧名思義,在發(fā)生錯誤時會產(chǎn)生error消息。如果把method_call看作打電話,那么signal消息就是來電了。后面還會詳細(xì)討論。
1.2、dbus-send和dbus-monitor
dbus提供了兩個小工具:dbus-send和dbus-monitor。我們可以用dbus-send發(fā)送消息。用dbus-monitor監(jiān)
視總線上流動的消息。讓我們通過dbus-send發(fā)送消息來調(diào)用前面的Add方法,這時dbus-send充當(dāng)了應(yīng)用程序B。用dbus-
monitor觀察調(diào)用過程中的消息。
啟動example-service:
在另一個控制臺啟動dbus-monitor:
dbus-monitor默認(rèn)監(jiān)視會話總線。執(zhí)行:
輸出為:
dbus-monitor的相關(guān)輸出包括:
:1.22就是dbus-send在本次調(diào)用中與會話總線所建立連接的唯一名。:1.21是連接“org.fmddlmyy.Test”的唯一名。
在以上輸出中我們可以看到:1.22向“org.fmddlmyy.Test”發(fā)送method_call消息,調(diào)用Add方法。
:1.21通過method_return消息將調(diào)用結(jié)果發(fā)回:1.22。其它輸出信息會在以后說明。
dbus-send的詳細(xì)用法可以參閱手冊。調(diào)用遠(yuǎn)程方法的一般形式是:
dbus-send支持的參數(shù)類型包括:string, int32, uint32, double, byte, boolean。
2、消息總線的方法和信號
2.1、概述
消息總線是一個特殊的應(yīng)用,它可以在與它連接的應(yīng)用之間傳遞消息??梢园严⒖偩€看作一臺路由器。正是通過消息總線,D-Bus才在一對一的通信協(xié)議基礎(chǔ)上實現(xiàn)了多對一和一對多的通信。
消息總線雖然有特殊的轉(zhuǎn)發(fā)功能,但消息總線也還是一個應(yīng)用。其它應(yīng)用與消息總線的通信也是通過1.1節(jié)的基本消息類型完成的。作為一個應(yīng)用,消息總線也提供了自己的接口,包括方法和信號。
我們可以通過向連接“org.freedesktop.DBus ”上對象“/”發(fā)送消息來調(diào)用消息總線提供的方法。事實上,應(yīng)用程序正是通過這些方法連接到消息總線上的其它應(yīng)用,完成請求公共名等工作的。
2.2、清單
消息總線對象支持第一講中提到的標(biāo)準(zhǔn)接口"org.freedesktop.DBus.Introspectable",我們可以調(diào)用org.freedesktop.DBus.Introspectable.Introspect方法查看消息總線對象支持的接口。例如:
輸出為:
從輸出可以看到會話總線對象支持標(biāo)準(zhǔn)接口“org.freedesktop.DBus.Introspectable”和接口
“org.freedesktop.DBus”。接口“org.freedesktop.DBus”有16個方法和3個信號。下表列出了
“org.freedesktop.DBus”的12個方法的簡要說明:
org.freedesktop.DBus.RequestName (in STRING name, in UINT32 flags, out UINT32 reply) | 請求公眾名。其中flag定義如下: DBUS_NAME_FLAG_ALLOW_REPLACEMENT 1 DBUS_NAME_FLAG_REPLACE_EXISTING 2 DBUS_NAME_FLAG_DO_NOT_QUEUE 4
返回值reply定義如下: DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 DBUS_REQUEST_NAME_REPLY_EXISTS 3 DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 |
org.freedesktop.DBus.ReleaseName (in STRING name, out UINT32 reply) | 釋放公眾名。返回值reply定義如下: DBUS_RELEASE_NAME_REPLY_RELEASED 1 DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2 DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3 |
org.freedesktop.DBus.Hello (out STRING unique_name) | 一個應(yīng)用在通過消息總線向其它應(yīng)用發(fā)消息前必須先調(diào)用Hello獲取自己這個連接的唯一名。返回值就是連接的唯一名。dbus沒有定義專門的切斷連接命令,關(guān)閉socket就是切斷連接。 在1.2節(jié)的dbus-monitor輸出中可以看到dbus-send調(diào)用消息總線的Hello方法。 |
org.freedesktop.DBus.ListNames (out ARRAY of STRING bus_names) | 返回消息總線上已連接的所有連接名,包括所有公共名和唯一名。例如連接“org.fmddlmyy.Test”同時有公共名“org.fmddlmyy.Test”和唯一名“:1.21”,這兩個名稱都會被返回。 |
org.freedesktop.DBus.ListActivatableNames (out ARRAY of STRING bus_names) | 返回所有可以啟動的服務(wù)名。dbus支持按需啟動服務(wù),即根據(jù)應(yīng)用程序的請求啟動服務(wù)。 |
org.freedesktop.DBus.NameHasOwner (in STRING name, out BOOLEAN has_owner) | 檢查是否有連接擁有指定名稱。 |
org.freedesktop.DBus.StartServiceByName (in STRING name, in UINT32 flags, out UINT32 ret_val) | 按名稱啟動服務(wù)。參數(shù)flags暫未使用。返回值ret_val定義如下: 1 服務(wù)被成功啟動 2 已經(jīng)有連接擁有要啟動的服務(wù)名 |
org.freedesktop.DBus.GetNameOwner (in STRING name, out STRING unique_connection_name) | 返回?fù)碛兄付ü娒倪B接的唯一名。 |
org.freedesktop.DBus.GetConnectionUnixUser (in STRING connection_name, out UINT32 unix_user_id) | 返回指定連接對應(yīng)的服務(wù)器進(jìn)程的Unix用戶id。 |
org.freedesktop.DBus.AddMatch (in STRING rule) | 為當(dāng)前連接增加匹配規(guī)則。 |
org.freedesktop.DBus.RemoveMatch (in STRING rule) | 為當(dāng)前連接去掉指定匹配規(guī)則。 |
org.freedesktop.DBus.GetId (out STRING id) | 返回消息總線的ID。這個ID在消息總線的生命期內(nèi)是唯一的。 |
接口“org.freedesktop.DBus”的3個信號是:
org.freedesktop.DBus.NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner) | 指定名稱的擁有者發(fā)生了變化。 |
org.freedesktop.DBus.NameLost (STRING name) | 通知應(yīng)用失去了指定名稱的擁有權(quán)。 |
org.freedesktop.DBus.NameAcquired (STRING name) | 通知應(yīng)用獲得了指定名稱的擁有權(quán)。 |
2.3、練習(xí)
讓我們來試試消息總線提供的方法。
2.3.1、從ListName到d-feet的基本邏輯
用dbus-send調(diào)用:
輸出為:
這是會話總線當(dāng)前已連接的連接名。在d-feet窗口的左側(cè)窗口顯示的就是ListNames返回的連接名。聰明的讀者也許已經(jīng)想到使用消息總線的
“org.freedesktop.DBus.ListNames”方法和各連接的
“org.freedesktop.DBus.Introspectable.Introspect”,我們就可以像d-feet一樣查看總線上所有連接
的所有對象的所有接口的所有方法和信號。
你的想法很好。但有一個問題,我們必須對連接中的對象調(diào)用“org.freedesktop.DBus.Introspectable.Introspect”方法。 ListNames只列出了連接名,我們怎么獲取連接中的對象路徑呢?
答案很簡單,如果我們不知道對象路徑就從根目錄開始吧。連接中的對象是按照樹型結(jié)構(gòu)組織的。我們遍歷連接的對象樹就可以找到所有的對象。調(diào)用對象的
“org.freedesktop.DBus.Introspectable.Introspect”方法就可以查看對象的所有接口的所有方法和信號。例
如:假設(shè)我們不知道連接"org.fmddlmyy.Test"里有什么對象,我們可以對根對象"/"執(zhí)行:
輸出為:
"org.fmddlmyy.Test"的對象樹的根節(jié)點只有一個子節(jié)點"TestObj",再查看"/TestObj":
輸出為:
作為一個練習(xí),讓我們來查看系統(tǒng)總線的上的bluez接口。執(zhí)行:
輸出為:
我們看到連接"org.bluez"。查看它的根對象:
輸出為:
接著查對象"/org":
輸出為:
接著查對象"/org/bluez":
輸出為:
我們看到了對象"/org/bluez"的所有接口。對象"/org/bluez"還有子節(jié)
點"service_audio"、"service_input"、"service_network"和"service_serial"。必要時我
們可以接著查下去。d-feet的基本邏輯就是這樣。后面我們會自己實現(xiàn)一個dteeth。dteeth是命令行程序,可以遍歷指定連接的對象樹,列出所
有對象的所有接口的方法和信號。
2.3.2、ListActivatableNames和服務(wù)器的自動啟動
運行:
和
返回的數(shù)據(jù)是一樣的。在我的電腦上返回的數(shù)據(jù)是:
我們也可以用python腳本調(diào)用ListActivatableNames。例如:寫一個叫dls.py的腳本:
運行:
輸出為:
使用python腳本調(diào)用dbus接口是不是很簡單。如果你看過dbus-glib的代碼(后面會講解),你對python的簡潔會有更深刻的感觸。如果你執(zhí)行:
你會得到:
這條命令的輸出與ListActivatableNames的輸出是不是基本相同?你能看懂上面這條命令嗎?它將"/usr/share
/dbus-1/services/"下所有文件交給grep篩選出包含“Name”的行。將包含“Name”的行交給awk處理,awk用"="作為列
分隔符,取出第二列然后交給sort排序后輸出。
"/usr/share/dbus-1/services/"目錄就是dbus放service文件的地方。需要自動啟動的服務(wù)器會在這個目錄放一個
service文件,例如:
Name是服務(wù)器的公共名,Exec是服務(wù)器的執(zhí)行路徑。在客戶請求一個服務(wù),但該服務(wù)還沒有啟動時。dbus會根據(jù)service文件自動啟動服務(wù)。我們再寫一個調(diào)用“org.fmddlmyy.Test”的Add接口的python腳本:
在啟動“org.fmddlmyy.Test”服務(wù)器前調(diào)用這個腳本
會得到錯誤輸出:
我們編輯一個service文件:
把這個文件放到"/usr/share/dbus-1/services/"目錄后,再執(zhí)行add.py:
這次dbus自動啟動了服務(wù)器,我們的客戶腳本得到了正確的輸出,你有沒有感到dbus的神奇?dbus在自動啟動服務(wù)器后,不會自動關(guān)閉。如果沒人管它,這個服務(wù)器會一直開著。
2.3.3、其它方法
再演示幾個“org.freedesktop.DBus”接口的方法。NameHasOwner判斷有沒有連接擁有指定的公共名:
輸出為:
GetNameOwner返回公共名對應(yīng)的唯一名:
輸出為:
GetConnectionUnixUser返回指定連接對應(yīng)的服務(wù)器進(jìn)程的Unix用戶id:
輸出為:
這就是我的用戶id:
GetId返回消息總線的ID:
輸出為:
3 結(jié)束語
這一集有一些python代碼。即使你沒有學(xué)過python,我也建議你看一看、試一試。其實我也沒有學(xué)過python。小時候,好像聽過什么德國
人一邊看說明書一邊開飛機(jī)的笑話。這或許是吹牛,但對于程序員來說一邊查手冊一邊用新語言寫一些簡單程序應(yīng)該不算困難。下一講我們要寫一個略大點的
python程序。雖然我習(xí)慣于C/C++的事必躬親,但不可否認(rèn)python確實是很有魅力的語言,難怪在嵌入式環(huán)境也有那么多人用python作原型
開發(fā)。
我想在freerunner(一個開源linux手機(jī))上查看fso(openmoko的諸多軟件版本之一)的dbus信
息。但fso的python沒有g(shù)tk模塊,跑不了d-feet。在上一講我介紹了d-feet的基本思路:用
“org.freedesktop.DBus.ListNames”枚舉消息總線上的連接,用
“org.freedesktop.DBus.Introspectable.Introspect”
從"/"開始遍歷連接的對象樹。上一講我們手工查看了兩個連接,那么我們能不能寫一個程序自動遍歷連接的對象樹,輸出指定連接的所有對象的所有接口的所有
方法和信號?
當(dāng)然可以,為此我寫了一個叫dteeth的python腳本。不過在介紹這個腳本前,讓我們先看看dbus的數(shù)據(jù)類型。
1、dbus的數(shù)據(jù)類型
dbus用xml描述接口,例如:
其實前兩講已經(jīng)看過很多例子了。node就是接口中的對象,node可以包含node,構(gòu)成對象樹。
dbus的接口描述文件統(tǒng)一采用utf-8編碼。我相信讀者很容易理解這個接口描述文件。我只想解釋一下描述參數(shù)數(shù)據(jù)類型的type域。
dbus的數(shù)據(jù)類型是由"s"或"a{sv}"這樣的類型簽名(Type Signatures)定義的。類型簽名中可以使用以下標(biāo)記:
a | ARRAY 數(shù)組 |
b | BOOLEAN 布爾值 |
d | DOUBLE IEEE 754雙精度浮點數(shù) |
g | SIGNATURE 類型簽名 |
i | INT32 32位有符號整數(shù) |
n | INT16 16位有符號整數(shù) |
o | OBJECT_PATH 對象路徑 |
q | UINT16 16位無符號整數(shù) |
s | STRING 零結(jié)尾的UTF-8字符串 |
t | UINT64 64位無符號整數(shù) |
u | UINT32 32位無符號整數(shù) |
v | VARIANT 可以放任意數(shù)據(jù)類型的容器,數(shù)據(jù)中包含類型信息。例如glib中的GValue。 |
x | INT64 64位有符號整數(shù) |
y | BYTE 8位無符號整數(shù) |
() | 定義結(jié)構(gòu)時使用。例如"(i(ii))" |
{} | 定義鍵-值對時使用。例如"a{us}" |
a表示數(shù)組,數(shù)組元素的類型由a后面的標(biāo)記決定。例如:
在以后的例子中,我們會親手實現(xiàn)上面這個xml描述的接口,包括服務(wù)器和客戶程序。到時候,讀者會對dbus的數(shù)據(jù)類型有更直觀的認(rèn)識。
2、dteeth
2.1、運行dteeth
可以從這里下載dteeth的源代碼。其中包含兩個python腳本:dteeth.py和_introspect_parser.py。 dteeth.py是我寫的。_introspect_parser.py是個開源模塊,可以分析Introspect返回的xml數(shù)據(jù)。
dteeth用法如下:
默認(rèn)連接session總線,除非你加上--system??梢砸淮沃付ㄍ幌⒖偩€的多個連接。先在PC上試一試:
我也在fso版本的freerunner手機(jī)上運行了一下,得到了org.freesmartphone.ogsmd的所有對象的所有的接口的所有方法和信號:
2.2、源代碼
下面是dteeth的源代碼:
dteeth是我寫的第一個超過10行的python腳本。對于熟悉python的讀者,dteeth應(yīng)該是很簡單的。不過我還是簡單解釋一下dteeth的主要邏輯。
2.3、dteeth的主要邏輯
main函數(shù)分析命令行,對命令行上指定的每個連接調(diào)用show_connection函數(shù)。 show_connection在打印連接名后調(diào)用show_obj從根對象"/"開始遍歷連接的對象樹。
show_obj對輸入對象調(diào)用Introspect方法,返回的xml數(shù)據(jù)交由_introspect_parser處理。
_introspect_parser會從xml數(shù)據(jù)中分出inerface和node。
show_obj對inerface調(diào)用show_iface顯示。 show_obj對node會遞歸調(diào)用show_obj,實現(xiàn)對象樹的遍歷。
2.4、_introspect_parser的輸出格式
_introspect_parser.process_introspection_data函數(shù)分析Introspect方法返回的xml數(shù)據(jù)。為了了解_introspect_parser的輸出格式,我們可以寫個小腳本:
可以用這個腳本直接打印process_introspection_data返回的數(shù)據(jù)。下面是整理后的輸出:
所有字符串前面都有前綴u,表示這些字符串都是Unicode編碼。在python中,字典用{},元組用(),列表用[]。從括號我們就能看出數(shù)據(jù)格式。
我們看到process_introspection_data返回返回一個字典。這個字典有兩個映射。一個映射的鍵值是"interfaces",另一個映射的鍵值是"child_nodes"。
3、python基礎(chǔ)
簡單介紹一下與dteeth有關(guān)的python語法。
3.1、代碼塊和縮進(jìn)
python用縮進(jìn)來區(qū)分語句所屬的代碼塊,從類定義、函數(shù)到for、if的代碼塊都是用縮進(jìn)來去區(qū)分的。沒有縮進(jìn)的代碼塊是腳本的主體代碼。一個
腳本文件也被稱作一個模塊。不管模塊被直接運行還是被其它模塊導(dǎo)入,主體代碼都會在載入時被執(zhí)行。例如dteeth的主體代碼只有兩句:
__xxx__這樣的標(biāo)志符通常是python的系統(tǒng)變量。如果模塊被導(dǎo)入,__name__的值是模塊的名字。如果模塊被直接執(zhí)行,__name__的值是"__main__"。我們通常在模塊被直接執(zhí)行時,調(diào)用主函數(shù)或模塊的測試函數(shù)。
3.2、腳本文件格式
python腳本的起始行通常是:/p>
env是一個可以修改環(huán)境變量并執(zhí)行程序的工具,它可以自動在系統(tǒng)路徑中搜索要執(zhí)行的程序。 python腳本文件必須以0A為換行符,默認(rèn)僅支持ASCII字符。如果要寫中文注釋(顯然是不提倡的),可以在起始行后用以下語句將文件指定為utf-8編碼:
這時,文件必須被保存為沒有BOM的utf-8編碼文件。
3.3、列表、元組和字典
列表類似于C的數(shù)組,列表元素用[]包括。元組是不可變的列表,元組元素用()包括。元組的元素個數(shù)和類型在創(chuàng)建后就不能改變了。元組中基本類型的值是不能改變的,但如果元組的一個元素是列表,我們可以改變列表內(nèi)容,即我們可以改變元組中可變元素的內(nèi)容。例如:
字典是鍵-值對的集合,字典元素用{}包括。
4、結(jié)束語
本文介紹了一個叫作dteeth的python腳本。這個腳本邏輯很簡單,讀者可以根據(jù)需要修改或擴(kuò)充。講了這么多dbus,我們還沒有接觸C代碼。下一講,我們討論dbus的C實例。
dbus-glib是dbus底層接口的一個封裝。本講我們用dbus-glib做一個dus接口,并寫一個客戶程序。
1、接口
1.1、編寫接口描述文件
首先編寫接口描述文件。我們要實現(xiàn)的連接的公共名是"org.freesmartphone.ogsmd",接口描述文件如下:
我們要在連接"org.freesmartphone.ogsmd"中實現(xiàn)對象"/org/freesmartphone/GSM
/Device"。這個對象有接口"org.freesmartphone.GSM.SMS"。這個接口有一個SendMessage方法和一個
IncomingMessage信號。
SendMessage方法和IncomingMessage信號除了兩個字符串參數(shù)外,還有一個a{sv}參數(shù),這是一個哈希表,即python
的字典。鍵-值對的鍵類型是字符串,值類型是VARIANT。這個接口是openmoko
fso接口的一部分。但為簡單起見,本例在哈希表部分,只用三個鍵值。
鍵"alphabet"對應(yīng)的值類型是字符串。
鍵"csm_num"對應(yīng)的值類型是INT32。
鍵"csm_seq"對應(yīng)的值類型是INT32。
請注意方法和信號名應(yīng)采用單詞連寫,首字母大寫的格式。
1.2、由接口描述文件生成綁定文件
有一個叫dbus-binding-tool的工具,它讀入接口描述文件,產(chǎn)生一個綁定文件。這個文件包含了dbus對象的接口信息。在主程序中我
們通過dbus_g_object_type_install_info函數(shù)向dbus-glib登記對象信息(DBusGObjectInfo結(jié)構(gòu))。
本例使用了autotool,在Makefile.am中可以這樣調(diào)用dbus-binding-tool:
"--prefix"參數(shù)定義了對象前綴。設(shè)對象前綴是$(prefix),則生成的DBusGObjectInfo結(jié)構(gòu)變量名就是
dbus_glib_$(prefix)_object_info。綁定文件會為接口方法定義回調(diào)函數(shù)。回調(diào)函數(shù)的名稱是這樣的:首先將xml中的方法名
稱轉(zhuǎn)換到全部小寫,下劃線分隔的格式,然后增加前綴"$(prefix)_"。例如:如果xml中有方法SendMessage,綁定文件就會引用一個名
稱為$(prefix)_send_message的函數(shù)。
綁定文件還會為接口方法生成用于散集(Unmarshaling)的函數(shù)。在dbus消息中,方法參數(shù)是以流格式存在的。該函數(shù)將方法參數(shù)由數(shù)據(jù)流
還原到glib的數(shù)據(jù)格式,并傳入方法的回調(diào)函數(shù)。本例中,dbus-binding-tool生成以下的smss-glue.h:
在包含綁定文件前,我們必須聲明綁定文件要引用的回調(diào)函數(shù)。
2 對象
2.1 對象定義
dbus-glib用GObject實現(xiàn)dbus對象。所以我們首先要實現(xiàn)一個對象。在本例中,我們實現(xiàn)一個GsmSms對象,它繼承了GObject:
GObject的對象定義雖然繁瑣,但有固定的套路。依樣畫葫蘆,畫多了就習(xí)慣了。我們在gsm_sms.h中聲明了
gsm_sms_send_message函數(shù)。
gsm_sms_send_message函數(shù)是在gsm_sms.c中實現(xiàn)的,在綁定文件smss-glue.h中用到。因為主程序要使用綁定文件中的
對象信息,所以應(yīng)由主程序包含綁定文件。主程序只要在包含綁定文件前包含gsm_sms.h,編譯器就不會抱怨gsm_sms_send_message
函數(shù)未聲明。
2.2 信號的列集函數(shù)
列集(Marshaling)是將數(shù)據(jù)從某種格式存為流格式的操作;散集(Unmarshaling)則是列集的反操作,將信息從流格式中還原出
來。在綁定文件中,dbus-binding-tool自動生成函數(shù)將方法參數(shù)從dbus消息中還原出來,即實現(xiàn)了散集。那么我們怎么把信號參數(shù)由
glib的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換到消息中的數(shù)據(jù)流呢?
因為GsmSms對象有一個信號,所以在對象類初始化函數(shù)gsm_sms_class_init中,我們要調(diào)用g_signal_new創(chuàng)建信號。 g_signal_new要求我們提供一個列集函數(shù)。
glib有一些標(biāo)準(zhǔn)的列集函數(shù),在gmarshal.h中定義。例如g_cclosure_marshal_VOID__STRING,這個函數(shù)適
合只有一個字符串參數(shù)的信號。如果gmarshal.h沒有提供適合的列集函數(shù),我們可以用一個叫g(shù)lib-genmarshal的工具自動生成列集函
數(shù)。后面我們會看到,無論是標(biāo)準(zhǔn)列集函數(shù)還是生成的列集函數(shù)都是既可以用于列集也可以用于散集,這些函數(shù)通常都被稱作列集函數(shù)。
使用glib-genmarshal前,我們同樣要準(zhǔn)備一個輸入文件:
我們需要的函數(shù)返回類型是VOID,參數(shù)是STRING,STRING,BOXED。在Makefile.am中可以這樣調(diào)用glib-genmarshal:
"--prefix"和函數(shù)原型決定輸出函數(shù)名。如果"--prefix=sms_marshal",函數(shù)原型
是"OID:STRING,STRING,BOXED",生成的列集函數(shù)名就必然是
sms_marshal_VOID__STRING_STRING_BOXED。
在本例中自動生成的文件內(nèi)容如下:
2.3 對象實現(xiàn)
準(zhǔn)備好列集函數(shù)后,我們來實現(xiàn)GsmSms。
在類初始化函數(shù)gsm_sms_class_init中,我們調(diào)用g_signal_new創(chuàng)建了信號。g_signal_new函數(shù)的原型是:
31行提供了列集函數(shù)。32-33行是返回值類型和參數(shù)類型。其中第三個參數(shù)調(diào)用了函數(shù)sms_get_features_type(在
sms_features.h中聲明)。因為a{sv}類型的參數(shù)處理起來比較繁瑣,我專門寫了一個sms_features模塊處理,后面會介紹。
在主程序中登記對象信息時,對象信息把SendMessage方法和gsm_sms_send_message函數(shù)以及自動生成的散集函數(shù)聯(lián)系起
來。當(dāng)客戶程序調(diào)用SendMessage方法時,dbus-glib會通過對象信息表格找到回調(diào)函數(shù)和散集函數(shù),用散集函數(shù)從method_call消
息中取出參數(shù)傳入回調(diào)函數(shù)gsm_sms_send_message。
gsm_sms_send_message調(diào)用sms_show_features函數(shù)處理a{sv}參數(shù)。
sms_show_features也在sms_features模塊定義,后面會介紹。
gsm_sms模塊提供了一個gsm_sms_emit_incoming_message函數(shù)供外部模塊調(diào)用。調(diào)用這個函數(shù)可以發(fā)射一個信號。在真實環(huán)境中,只有外部事件發(fā)生后才會發(fā)射信號。本例中會有測試代碼發(fā)射信號。
3 主程序
3.1 登記dbus服務(wù)器
下面就是主程序
93行調(diào)用dbus_g_object_type_install_info登記GsmSms類的接口信息。97行連接會話總線。
101-102行在會話總線上為連接"org.freedesktop.DBus"的"/"對象的接口"org.freedesktop.DBus"建立
代理。 104-109行通過接口代理調(diào)用"RequestName"方法,請求公共名"org.freesmartphone.ogsmd"。
請求公共名成功后,112行建立GsmSms對象。113行登記GsmSms對象,登記時指定對象路徑"/org/freesmartphone/GSM/Device",并傳入對象指針。118行進(jìn)入主循環(huán)等待客戶消息。
3.2 IO Channel
我想增加一個敲鍵測試信號發(fā)射。但我又必須在glib主循環(huán)里等待dbus消息。怎樣才能既等待dbus消息,又等待敲鍵呢?這種情況可以使用
glib的IO Channels。glib的IO Channels允許我們在glib主循環(huán)等待指定的文件或socket句柄。
要使用IO Channels,首先包含"glib/giochannel.h"。116行用句柄0(即標(biāo)準(zhǔn)輸入)創(chuàng)建一個GIOChannel。 117行為我們創(chuàng)建的GIOChannel登記回調(diào)函數(shù)。我們在回調(diào)函數(shù)channel_cb中處理敲鍵,發(fā)射信號。
3.3 編譯運行
讀者可以從這里下載完整的示例程序。下集會介紹本例的autotool工程。目前,我們先編譯運行一下,解壓后執(zhí)行:
鍵入h后回車,可以看到敲鍵的幫助信息。
我想找個客戶程序測試一下,dbus-send不能發(fā)a{sv}這樣復(fù)雜的參數(shù)。我們可以用d-feet測試,或者寫個python腳本:
執(zhí)行smsc.py,在服務(wù)器端看到:
說明服務(wù)器可以正常工作。主程序的89行要求glib直接用malloc分配內(nèi)存,這樣用valgrind才能檢查到內(nèi)存泄漏(如果有的話)。我們可以這樣運行smss以檢查是否有內(nèi)存泄漏:
4、復(fù)雜的數(shù)據(jù)類型
在dbus中怎樣處理復(fù)雜的數(shù)據(jù)類型?第一個建議是盡量不要使用復(fù)雜的數(shù)據(jù)類型。但如果確實需要呢?有的網(wǎng)友建議用GArray作為容器,不管什么參數(shù),在客戶端都手工放入GArray,在服務(wù)器端再自己取出來。這確實是個思路,比較適合服務(wù)器和客戶端都是自己開發(fā)的情況。還有一篇"How to pass a variant with dbus-glib" 介紹了怎樣用GValue傳遞復(fù)雜的數(shù)據(jù)類型,讀者可以參考。
下面看看在我們的例子中是怎樣處理a{sv}參數(shù)的:
sms_features.h聲明了幾個函數(shù)。這個例子的服務(wù)器、客戶端都會調(diào)用。以下是這些函數(shù)的實現(xiàn):
sms_get_features_type調(diào)用dbus_g_type_get_map創(chuàng)建a{sv}類型。服務(wù)器在創(chuàng)建信號時用到??蛻舳嗽谡{(diào)用方法和注冊信號時都會用到。
sms_create_features調(diào)用g_hash_table_new_full創(chuàng)建哈希表,在創(chuàng)建的同時登記了值對象的清理函數(shù)。在sms_release_features調(diào)用g_hash_table_destroy銷毀哈希表時,創(chuàng)建時登記的值對象清理函數(shù)會被調(diào)用。
5、客戶端
5.1、代碼
客戶端程序如下:
112行連接會話總線。116-118行在會話總線上獲取連接"org.freesmartphone.ogsmd"的對象"/org/freesmartphone/GSM/Device"
的接口"org.freesmartphone.GSM.SMS"的接口代理對象。
123行調(diào)用dbus_g_object_register_marshaller向dbus-glib登記列集函數(shù)。
125行調(diào)用dbus_g_proxy_add_signal增加對信號IncomingMessage的監(jiān)聽。126行登記信號IncomingMessage的回調(diào)函數(shù)。
123行登記的還是我們用glib-genmarshal生成的函數(shù)sms_marshal_VOID__STRING_STRING_BOXED。
dbus-glib使用這個函數(shù)從signal消息中取出信號參數(shù),傳遞給回調(diào)函數(shù),即執(zhí)行散集操作。這說明glib-genmarshal生成的列集函數(shù)既可以用于列集,也可以用于散集。
客戶端程序同樣用IO
Channel接受用戶輸入。129行在登記回調(diào)函數(shù)時將指向接口代理對象的指針作為參數(shù)傳入?;卣{(diào)函數(shù)channel_cb在用戶鍵入's'命令后通過send_message函數(shù)調(diào)用org.freesmartphone.GSM.SMS接口對象的SendMessage方法。
107行的設(shè)置G_SLICE_CONFIG_ALWAYS_MALLOC同樣是為了用valgrind檢查內(nèi)存泄漏。
5.2、執(zhí)行
我們先運行
dbus-monitor,然后運行smss,再運行smsc。先在smsc中鍵入's'回車調(diào)用SendMessage方法。然后在smss中鍵入's'回車發(fā)送IncomingMessage信號。然后在smsc中鍵入'q'回車退出。最后在smss中鍵入'q'回車退出。
我們可以看到打印出來的信號和消息。對于同一件事情,不同的層次的觀察者會看到不同的細(xì)節(jié),下表是dbus-monitor看到的東西:
smss連接會話總線。會話總線發(fā)NameOwnerChanged信號,通知唯一名":1.21"被分配。 | signal
sender=org.freedesktop.DBus -> dest=(null destination)
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=NameOwnerChanged string ":1.21" string "" string ":1.21" |
smss向會話總線發(fā)送Hello取得自己的唯一名":1.21"。 | method
call sender=:1.21 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello |
smss調(diào)用AddMatch要求接收會話總線的NameOwnerChanged信號。 | method
call sender=:1.21 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=AddMatch string
"type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" |
smss調(diào)用AddMatch要求接收會話總線發(fā)送的所有信號。 | method
call sender=:1.21 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=AddMatch string "type='signal',sender='org.freedesktop.DBus',path='/',interface='org.freedesktop.DBus'" |
smss調(diào)用GetNameOwner獲取連接"org.freedesktop.DBus"的唯一名。 | method
call sender=:1.21 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=GetNameOwner string "org.freedesktop.DBus" |
會話總線發(fā)送NameOwnerChanged信號,通知唯一名為":1.21"的連接獲得了公眾名"org.freesmartphone.ogsmd"。 | signal
sender=org.freedesktop.DBus -> dest=(null destination)
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=NameOwnerChanged string "org.freesmartphone.ogsmd" string "" string ":1.21" |
smss請求公眾名"org.freesmartphone.ogsmd"。分配公眾名在前,請求公眾名在后,應(yīng)該是監(jiān)控過程顛倒了消息次序。 | method call sender=:1.21 -> dest=org.freedesktop.DBus path=/; interface=org.freedesktop.DBus; member=RequestName string "org.freesmartphone.ogsmd" uint32 0 |
smsc連接會話總線。會話總線發(fā)NameOwnerChanged信號,通知唯一名":1.22"被分配。 | signal
sender=org.freedesktop.DBus -> dest=(null destination)
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=NameOwnerChanged string ":1.22" string "" string ":1.22" |
smss向會話總線發(fā)送Hello取得自己的唯一名":1.22"。 | method
call sender=:1.22 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello |
smsc調(diào)用AddMatch要求接收會話總線的NameOwnerChanged信號。 | method
call sender=:1.22 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=AddMatch string
"type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" |
smsc調(diào)用AddMatch要求接收連接'org.freesmartphone.ogsmd'中對象'/org/freesmartphone/GSM/Device'的'org.freesmartphone.GSM.SMS'接口的信號。 | method
call sender=:1.22 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=AddMatch string
"type='signal',sender='org.freesmartphone.ogsmd',path='/org/freesmartphone/GSM/Device',interface='org.freesmartphone.GSM.SMS'" |
smsc調(diào)用GetNameOwner獲取連接"org.freesmartphone.ogsmd"的唯一名。 | method
call sender=:1.22 -> dest=org.freedesktop.DBus
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=GetNameOwner string "org.freesmartphone.ogsmd" |
smsc調(diào)用連接'org.freesmartphone.ogsmd'中對象'/org/freesmartphone/GSM/Device'的'org.freesmartphone.GSM.SMS'接口的SendMessage方法。 | method
call sender=:1.22 -> dest=org.freesmartphone.ogsmd
path=/org/freesmartphone/GSM/Device;
interface=org.freesmartphone.GSM.SMS; member=SendMessage string "10987654321" string "hello world" array [ dict entry( string "csm_seq" variant int32 2 ) dict entry( string "alphabet" variant string "gsm" ) dict entry( string "csm_num" variant int32 8 ) ] |
smss向smsc發(fā)送method return消息,返回SendMessage方法的輸出參數(shù)。 | method return sender=:1.21 -> dest=:1.22 reply_serial=5 int32 11 |
smss發(fā)送IncomingMessage信號。 | signal
sender=:1.21 -> dest=(null destination)
path=/org/freesmartphone/GSM/Device;
interface=org.freesmartphone.GSM.SMS; member=IncomingMessage string "12345678901" string "hello signal!" array [ dict entry( string "csm_seq" variant int32 1 ) dict entry( string "alphabet" variant string "ucs2" ) dict entry( string "csm_num" variant int32 3 ) ] |
會話總線通知連接":1.22",即smsc的連接已經(jīng)切斷。 | signal
sender=org.freedesktop.DBus -> dest=(null destination)
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=NameOwnerChanged string ":1.22" string ":1.22" string "" |
會話總線通知擁有公共名"org.freesmartphone.ogsmd"的連接已經(jīng)切斷。 | signal
sender=org.freedesktop.DBus -> dest=(null destination)
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=NameOwnerChanged string "org.freesmartphone.ogsmd" string ":1.21" string "" |
會話總線通知擁有唯一名":1.21"的連接已經(jīng)切斷。即smss已經(jīng)終止。 | signal
sender=org.freedesktop.DBus -> dest=(null destination)
path=/org/freedesktop/DBus; interface=org.freedesktop.DBus;
member=NameOwnerChanged string ":1.21" string ":1.21" string "" |
6、工程
我提供下載的文件要用make distcheck制作的,其中包含了一些自動生成的文件。執(zhí)行./clean.sh可以刪掉自動生成的文件,只留下我創(chuàng)建的文件:
前面已經(jīng)介紹過所有的源文件。我們再看看工程文件:
autogen.sh建立工程環(huán)境。在執(zhí)行clean.sh后,執(zhí)行autogen.sh重新生成configure等工程文件。其中的touch命令是為了防止文件有將來的時間戳。因為我在虛擬機(jī)中運行ubuntu,所以可能會出現(xiàn)這類問題。
Makefile.am將autogen.sh
clean.sh也作為發(fā)布文件。最重要的工程文件是"configure.ac"和"src/Makefile.am"。
6.1、configure.ac
8-17行檢查dbus庫,它們會生成編譯常數(shù)DBUS_CFLAGS和DBUS_LIBS。 20-30行檢查dbus-glib庫,它們會生成編譯常數(shù)DBUS_GLIB_CFLAGS和DBUS_GLIB_LIBS。
6.2、src/Makefile.am
19-20行由接口描述文件smss.xml生成存根文件smss-glue.h。22-26行由列集接口定義生成包含列集函數(shù)的代碼。
7、結(jié)束語
本文介紹了一個簡單的dbus-glib的例子,包括服務(wù)器和客戶端。第一講中還有一個加法例子,如果你理解了本文的例子,那個例子就更簡單了。 dbus-glib源代碼中有兩個例子:
example-service和example-client演示方法調(diào)用。這個例子的接口描述文件中有個參數(shù)類型寫錯了,將(us)寫成(ss),運行時會出錯??赡茏髡呦胙菔疽幌陆涌诙x與代碼實現(xiàn)不一致的后果吧。讀者可以從這里下載我修改過的代碼。
example-signal-emitter和example-signal-recipient演示信號發(fā)射。這個例子中,example-signal-recipient調(diào)用example-signal-emitter的方法請求發(fā)送信號。實際上信號應(yīng)該是來自服務(wù)器側(cè)的信息。我將其改成在example-signal-emitter中敲鍵發(fā)送信號。讀者可以從這里下載我修改過的代碼。
好了,《dbus實例講解》到此結(jié)束。其實我的所有文章只是希望能讓這復(fù)雜的世界簡單一點。