天天看點

DBus介紹

一篇關于DBus的入門介紹,澄清了一些D-Bus中容易混淆的概念

意譯:freeworkzz 

日期:2010-07-21

來源:http://www.freedesktop.org/wiki/IntroductionToDBus

關于本文

本文不是教程,更不是手冊。它不會教你如何使用D-Bus,也不會教你如何安裝及如何寫基于D-BUS的程式。

這裡有的,是解釋了D-Bus到底是什麼,其背後的概念以及如何将這些概念集合在一起,還有一些必須了解的術語。這裡沒有不必要的技術細節,也并不關心讀者所使用的程式設計語言。初學者可以在閱讀其他教程或指南前先參考本文,它将告訴你使用D-Bus可以做什麼。

即使讀者已經有了一本好的教程,先讀一讀本文也是有好處的。在D-Bus的世界中,有很多專用術語,它們與通常的概念并不完全一緻。這篇文章将從最基礎開始解釋這些術語,進而避免了解上的偏差。本文也嘗試從不同角度來看D-Bus,避免隻專注于一種程式設計語言來闡述時對使用其他語言的讀者産生排斥。

D-Bus概述

D-Bus是一種本地程序間通信機制(不同主機間的程序通信支援可能會在将來加入,但這并不是D-Bus最初設計思想)。D-Bus的特點是輕量級、快速,為主流桌面環境提供統一的程序間通信界面。

與其他重量級的程序間通信技術不同,D-Bus并未使用會話進行通信。D-Bus使用了狀态以及連接配接的概念,使其比UDP等低級的資訊傳輸協定更“聰明”。另一方面,它傳送的是離散消息,這又與TCP協定将資料看做“流”有所不同。D-Bus支援點對點的傳信,以及廣播/訂閱式的傳信兩種傳信方式。

D-Bus将要傳輸的資料結構化為二進制資料,包括不同長度的整數、浮點數、字元串、複合類型等。因為所有的傳輸資料都是由一定規則構成,不符合規則的資訊可以簡單地忽略掉。通俗地說,D-Bus就是整套程序通信機制的指揮官。

與程式設計語言的綁定

D-Bus在很多不同的程式設計語言上都有其接口實作。這些接口封裝了D-Bus低級API,提供了更符合此程式設計語言的文法結構。

與一般的通信概念相比,使用D-Bus時應該更傾向于使用面向對象的思維方式。在一些語言中,使用者甚至很難标察覺到D-Bus的存在,他們感覺就好像是在使用語言原生元件一樣(這裡的“元件”指的是庫、子產品、包、對象、函數等)。是以,D-Bus在不同語言的實作接口差别很大,而且在一些語言中顯得很簡單。

實作D-Bus接口的語言正在逐漸增加。在C語言中,有最低級的API,但其實作及使用上非常複雜。C語言中另一個實用化的實作基于GLib。在Java、Perl、Python等都有D-Bus接口。

Buses(總線)

D-Bus有兩個主要組成部分:一是點對點通信的支援庫,任何想用D-Bus通信的程序都可以引用;二是dbus服務程序,其作用類似于總線,程序可以連接配接到這個總線,并在總線上傳輸消息。典型的使用流程是:程序使用支援庫,連接配接到服務程序總線,并收發消息。

一個系統中可以同時存在多條D-Bus總線。D-Bus最初用來替代GNOME桌面環境中的CORBA式程序通信元件。與KDE使用的DCOP類似,D-Bus正在成為主流桌面環境甚至其他平台的标準配件。GNOME一般使用兩類總線:一條系統級總線,提供類似硬體變更之類消息;另外,每個使用者都有一條使用者會話總線。每條會話總線僅供單獨的使用者使用,同時D-Bus也提供了使用者權限機制供系統總線使用。

Addresses(位址)

每條總線都有總線位址,程序通過這個位址連接配接到總線。總線位址一般是類似于”/tmp/.hiddensocket”這類Socket接口,但也有可能是TCP端口,或者其他通信方式接口。具體如何使用這些接口通信,是完全封裝在dbus支援庫中。我們一般隻說客戶程序打開并使用到總線的連接配接。

設定與啟動

總線服務使用dbus-launch指令啟動。它使用–config-file選項指明所使用的總線設定檔案。預設的設定檔案位于/etc/dbus-1/system.conf與

/etc/dbus-1/session.conf,分别對應系統總線與會話總線。

設定檔案使用的是XML格式。

Connections(連接配接)

總線上的每個連接配接都有一個或多個名字。這些名字一般叫做連接配接名(connection’s bus names 或簡單地稱為 bus names(注:bus name指的是連接配接名,而不是總線名。))。連接配接名由”.”分開的字元組成,比如”com.acme.Foo”,中間的字元可以是字母、數字、連接配接線、下劃線。每個連接配接擁有一個連接配接名。

當連接配接建立以後,D-Bus服務會配置設定一個不可改變的連接配接名,稱為唯一連接配接名,這個連接配接名即使在程序結束後也不會再被其他程序所使用。唯一連接配接名以冒号開頭,像是這個樣子”:34-907″(後面的數字沒有任何意義,僅用來區分不同連接配接)

此外,連接配接還可以引證另外的連接配接名,比如提供服務的程序可以起一個大家都知道的連接配接名,以便使用其服務的客戶程序連接配接。這個名字必須包含兩個以上的”.”,比如:”com.acme.PortableHole”。與唯一名不同的是,這個連接配接名可以轉給其他程序使用。

Object Model(對象模型)

類似TCP及UDP協定中,資訊的交換是對等的;資料總是從一個端口傳輸到另一個端口。D-Bus使用了複雜一些的模型,資訊的發送方與資訊的接收方永遠不同。

以下我們将借用面向對象中的名詞。其中很多如“對象(object)”及“方法(method)”等在D-Bus中具有特殊的意義,不要與具體語言中的相同名詞混淆。

Objects(對象)

總線中資訊流向的端點在D-Bus中稱為對象。對象由客戶程序建立,并在連接配接程序中保持不變。對象是客戶程序提供服務的方式,當然客戶程序可以建立多個對象。

總線的通信以對象為中心,其上的任何資訊不外乎以下三類:

發送到對象上的請求;

從對象到請求程序的請求的響應;

單向的廣播消息,發送到之前注冊過這類消息的程序。

是以,從更高的角度看,總線支援兩種形式的通信,“一對一的請求-響應”發送到對象,及來自于對象的“一對多的廣播-訂閱”

每條總線至少都有一個對象,這個對象代表總線本身。客戶程序可以通過向這個總線對象發送請求來得到目前總線的狀态。後面你将看到,總線對象有更多有用的功能。

Proxies(代理)

總線上的對象一般通過代理來通路。總線上的對象位于客戶程序以外,而客戶可以調用的本地接口與對象通信,此時,本地接口充當了代理的角色。是否要區分代理與對象的概念取決于你如何使用D-Bus。在Java語言中并不區分這兩個概念,使你好像是在直接操作本地子產品。但Glib的包裝中明顯地區分了代理與對象,并且提供了兩類代理類型。代理隻存在于客戶程序的本地代碼中,如何使用代理由所使用的程式設計語言決定。

對象擁有名稱,它的構成類似于Unix檔案系統中的檔案路徑,是以通常也稱為路徑(path)。如代表電子表格中某一欄内容的對象名稱可能為”/org/kde/kspread/sheets/3/cells/4/5″。對象名在客戶連接配接中要保持唯一性。為了取得電子表格中這欄内容的代理通路,你需要在電子表格的連接配接中請求”/org/kde/kspread/sheets/3/cells/4/5″對象。

既然任何對象都存在于連接配接之上,它的名稱由連接配接名與對象本身名稱組成。一旦你找到了你需要的對象,并想在程式過程中一直使用這個對象,你一般需要儲存這個對象的代理變量。通過代理變量,你可以在以後操作這個對象。

一些語言的代理支援“斷線重連”。比如你所連接配接的對象在某段時間裡暫時斷開了與總線的連接配接,代理會自動重連到相同的連接配接名并重新找到對象,你的程式甚至不會知道目标對象有段時間不可用。并不是所有的語言都支援這一特性,在GLib中的兩種代理中的一種支援。但這一特性也并不總是理想的,比如你的一系列請求具有前後關聯性,此時目标對象的斷開與重新連接配接顯然打破了這種連續性。在這種性況下,你應該使用連接配接的唯一名稱進行連接配接(因為,唯一名稱在斷線重連後一定與之前的不同)。

Methods(方法)

客戶向某對象發送一個請求的過程,看起來就像在這個對象上執行了一個方法:對象被請求執行一個明确的,有名稱的動作。如果客戶請求執行一個目标對象未提供的方法,将會産生一個錯誤。

方法的定義中可以支援輸入參數。對于每個請求,都有一個包含請求結果以及結果資料(輸出參數)的響應傳回給請求者。當請求無法完成時,響應中将包含異常資訊,其中至少有異常名稱以及錯誤資訊。

大多數語言都将這些封裝在自身的語言機制中,比如将參數包裝進消息包,将異常資訊轉換成語言自身的異常等等。在這些實作中,向遠端對象傳遞一個字元串參數就好像是在本地執行一個字元串參數的函數一樣簡單。此時不再需要資料類型轉換、資料複制等繁瑣工作,語言本身封裝了一切低層實作。

當然,請求與傳統函數調用也有一個有趣的不同:當發送一個請求後,你不必一直等待響應。在複雜的程式中,你可以在響應到達之前去做其他的事。比如你可以去響應使用者動作,或者處理網絡/檔案中的資料。你甚至可以同時連續發送請求後再處理響應,而不是以發送-響應-再發送的順序。這種在等待響應過程中可以去做其他事情的調用,稱為異步調用。如果你使用簡單的發送-等待形式(同步調用),其他消息将會進入隊列,當你空閑的時候處理。

Signals(信号)

信号,這種通信形式依然遵從面向對象概念,它是從對象發出但沒有特定目的位址的單向資料廣播。客戶程序可以預先注冊其感興趣的信号,如特定名稱的信号或從某個對象發出的信号等。當對象發出信号後,所有訂閱了該信号的客戶将收到此信号的複本。接收端可能有多種情況出現,或者有一個客戶,或者有多個客戶,或者根本沒有客戶對這個信号感興趣。對于信号來說沒有響應消息,發出信号的對象不會知道是不是有客戶在接收,有多少客戶接收,以及從用戶端收到任何回報。

與方法類似的是,信号可以有參數。由于信号是單向通信,是以其不可能像方法一樣具有輸入輸出參數。D-Bus的最新版本允許客戶通過參數比對過濾其需要的信号。

信号一般用來廣播一些客戶可能會感興趣的事件,比如某個其他的客戶與總線的連接配接斷開等。這些信号來自總線對象,是以從信号中客戶可以分辨斷線是由于正常退出、被殺掉或者程式崩潰。

Interfaces(接口)

我們已經知道,每個對象都具有一些方法、以及可以産生特定的信号。方法與信号的集合稱為這個對象的成員。

對象的所有成員都通過接口來定義。與JAVA類似,接口就是成員聲明的集合。接口的實作就等于說這個接口提供了其所有方法及信号。所有成員必須按照接口聲明的那樣來提供服務。

對象可以都實作同一個接口,這與JAVA中不同的類可以實作同一個接口相似。另一方面,某個對象可以同時實作多個接口。與JAVA有所不同的是,D-Bus中沒有實作任何接口的對象是沒有意義的。對象所有支援的接口集合被稱為對象的類型(object’s type)

當客戶程序要執行一個對象方法或等待信号時,它必須指明要使用的對象及其對象成員。除此以外,客戶可能還要指明對象成員所在的接口,有時這是必須的步驟。比如,當對象同時在其兩個接口都實作foo方法時,若客戶不指明要執行的是哪個接口上的foo方法,則會産生歧義。D-Bus的實作也可能會直接拒絕這類歧義請求。同樣地,如果在訂閱信号時不指明接口,則也有可能會收到不同接口發來的相同名字的信号,這通常不是預期的情況。D-Bus的較老版本中存在一個當出現歧義時丢失消息的BUG。

至于同一個接口内的對象是否可以重載,即接口内是否允許相同名字的對象取決于程式設計語言的實作。

總結

讓我們來總結一下客戶程式從還未連接配接到總線,直至找到其所需要的方法或訂閱信号的整個流程。其中除了接口我們是可以忽略以外,其他步驟都是必須的。

名稱 表示方法 看起來就是這樣 由誰管理
總線 位址 unix:path=/var/run/dbus/system_bus_socket 系統設定
連接配接 連接配接位址

:34-907 (唯一名)

com.mycompany.TextEditor (公共名)

D-Bus(唯一名)

服務程序 (公共名)

對象 路徑 /com/mycompany/TextFileManager 服務程序
接口 接口名 org.freedesktop.Hal.Manager 服務程序
成員 成員名 ListNames 服務程序

由上表可以看出,大多數名稱都使用路徑格式來表示。這很容易造成一定的困擾,尤其是這些名稱取得很相像時,比如連接配接名為org.freedesktop.Hal,提供的對象為/org/freedesktop/Hal/Manager,實作的接口為org.freedesktop.Hal.Manager。另外,一些名稱用斜線分隔另一些使用點号,也需要适應一段時間。

資訊順序

對于同一個對象的請求的到達順序總會與其發送順序一緻。同樣地,對同一目标的響應也會與對象發送順序一緻。

但這并不意味所有的消息都會按照發送時間的順序到達。比如兩個客戶程序同時向同一個對象發送請求時,對象的接收順序是不确定的。再比如客戶向同一個對象連續發送兩個請求,相應的兩個響應順序也是不确定的。對象本身的實作可能使用多線程或内部消息優先級等機制打亂消息的處理順序

TODO: 線程

啟動

到目前為止,我們所舉的例子都是基于對象已經被服務程序建立的情況。除此之外,還一另外一種提供服務的方式:D-Bus服務程序可以按需要啟動對應服務。有兩種啟動方式,它們都需要請求服務的客戶提供要連接配接的公共名:

1. 使用總線對象所提供的方法

2. 使用連接配接公共名,直接調用目标連接配接上的對象方法

後一種方法可以在消息中設定參數來禁止。當你在建立代理而代理所對應的目标服務不存在時,一些程式設計語言會試圖啟動對應的服務,另一些語言會在你調用對象方法時再啟動對應服務。這對于等待對象信号的情況有很大不同,如果目标對象根本不存在,你可能會永遠等不到信号。

要建立可以自動啟動的服務,需要設定服務配置檔案。服務配置以UTF-8文本形式存放在.service檔案中。

比如,如果你的程式提供/usr/local/bin/bankcounter對象并使用com.bigmoneybank.Deposits,com.bigmoneybank.Withdrawals兩個連接配接名。則你可以像下面這樣寫一個”bankcounter.service”檔案(檔案名是任意的,但必須以.service結尾)

# (井号開頭是注釋)

# 下面一行是固定格式不要改動

[D-BUS Service]

Names=com.bigmoneybank.Deposits;com.bigmoneybank.Withdrawals

Exec=/usr/local/bin/bankcounter

Names一行指定了服務的連接配接名,以分号分隔。Exec一行指定啟動這個服務的可執行檔案路徑。

這些服務配置檔案所放置的位置在D-Bus設定檔案的塊中定義,預設為/usr/share/dbus-1/services/。如果在D-Bus運作過程中加入新的服務配置,則自動生效,無需重新開機。

關于這部分的進一步讨論參見Rapha?l Slinckx的DBus啟動教程(DBus Activation Tutorial)

未來計劃

這篇文檔還未完工。一些主題還沒有談到,包括:

對象的實作

特殊的連接配接名:org.freedesktop.DBus(連接配接名就是方法名,無需對象!)

身份認證(參考這裡:http://www.redhat.com/magazine/003jan05/features/dbus/)

Introspection

來源:http://www.freeworkzz.com/html/2010/07/dbus%E4%BB%8B%E7%BB%8D.html