1. 程式人生 > >DBUS(2)

DBUS(2)

一、概述

      官方網站:http://www.freedesktop.org/wiki/Software/dbus,但是如果要下windows版的程式碼最好不要從sourceforge下,多次下來的1.2.4版本都無法正常解壓。可以從svn上拿,具體見後面的dbus編譯部分。

      從官方首頁中可以看到這樣一段描述D-BUS 的話:“D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to interprocess communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a "single instance" application or daemon, and to launch applications and daemons on demand when their services are needed. ”

      因此,D-BUS從本質來說就是程序間通訊(inter-process communication)(IPC)的一個實現。他最初產生於Linux平臺,是做為freedesktop.org專案的一部分來開發的。正開始深入地滲透到 Linux 桌面之中。已經在Qt4,GNOME,Windows以及Maemo實現。在KDE4中已經取代了著名的DCOP,在GNOME取代笨重的Bonobo。在嵌入式系統中常用來實現C/S結構。

作為一個IPC,他實現了兩點:

      1.在同一個桌面會話中不同的應用程式進行通訊:系統匯流排(system bus),這個匯流排由

作業系統和後臺程序使用,安全性非常好,以使得任意的應用程式不能欺騙系統事件。應用程式可以直接和系統匯流排通訊,但是傳送的訊息受限制。

        2.桌面程式與核心或守護程序進行通訊:會話匯流排(Session bus),屬於登入使用者私有。它是使用者的應用程式用來通訊的一個會話匯流排。

二、D-BUS 特性

     1.D-BUS的協議是低延遲而且低開銷的,設計得小(但是程式碼量不算很少吧)而且高效,以便最小化傳送時間。從設計上避免往返互動並允許非同步操作。

     2.協議是二進位制的,而不是文字,這樣就排除了費事的序列化過程(我們的萬能引數序列化就比較佔時間)。

     3.考慮了位元組順序問題。

     4.易用性:它按照訊息而不是位元組流來工作,並且自動地處理了許多困難的IPC問題,並且D-Bus庫以可以封裝的方式來設計,這樣開發者就可以使用框架裡存在的物件/型別系統,而不用學習一種新的專用於IPC的物件/型別系統。

     5.請求時啟動服務以及安全策略。

     6.因為是做為freedesktop.org專案來開發的,有許多達人蔘加,所以質量應該是很有保證的。

     7.支援多語言(C/C++/Java/C Sharp/Python/Ruby),多平臺(Linux/windows/maemo)。

     8.採用C語言,而不是C++。

     9.由於基本上不用於internet上的IPC,因此對本地IPC進行了特別優化。

     10.提供服務註冊,理論上可以進行無限擴充套件。

    三、構架

      分成三層:

                a.libdbus庫,實現了底層的API以及協議,他除了需要XML解析器以外沒有必須的依賴。對於不同的語言,協議可能被重新實現。這個庫是一個基礎,雖然官方說他不是設計給應用程式呼叫的,但是實際上應用程式是可以直接呼叫的,特別是windows版,後面的使用分析中的例子就能看到;

                b.訊息守護程序,建立在libdbus的基礎上,可以管理多個應用程式之間的通訊。每個應用程式都和訊息守護程序建立dbus連結,由訊息守護程序統一進行訊息的派發;

                c.各種包裝庫,綁定了一些常見的框架(qt,Glib,Java,Python,C sharp etc.)沒什麼新功能,只是對dbus進行了一層封裝。方便使用官方建議應用程式使用這層進行呼叫;

體系結構圖(來自網路)

D-BUS分析—1 介紹 - yolcy - yolcy的部落格

 

      一、名詞解釋

  1. 訊息 

          訊息由頭部和訊息體組成,如果你把訊息當作一個package,那頭部就是地址,訊息體就是包的內容。訊息傳送系統使用頭部的資訊來知道把訊息送往何處,如何解釋訊息,接收者則解釋訊息體。訊息體可以沒有或有多個引數,這些引數是具有型別的值,如integer或byte陣列。訊息頭和訊息體都使用相同型別的type系統及格式來序列化資料。每種值的型別都有wire格式,從某種別的表示把值轉換為wire格式叫作列集(marshalling),而把wire格式轉回去則叫散集(unmarshalling).訊息頭部是有固定簽名和意義的值塊。訊息體是另外的值塊,帶有在頭部中指定的簽名。頭部必須位元組對齊,這樣當在一個緩衝區中存貯整個訊息時可以允許訊息體以8對齊開始。如果頭部不是自然地終止於8位元組邊界上,則必須加上最多7位元組的nul初始化的對齊填充。訊息體不需要位元組對齊。訊息的最大長度,包括頭,頭對齊填充以及訊息體是2的27次冪即134217728。實現不能傳送或接收超過此大小的訊息。頭部的簽名是: "yyyyuua(yv)",以更為可讀的方式寫出是:

    BYTE, BYTE, BYTE, BYTE, UINT32, UINT32, ARRAY of STRUCT of (BYTE,VARIANT)
    含義(摘自D-BUS spec:http://dbus.freedesktop.org/doc/dbus-specification.html):
    Value Description
    1st BYTE Endianness flag; ASCII 'l' for little-endian or ASCII 'B' for big-endian. Both header and body are in this endianness.
    2nd BYTE Message type. Unknown types must be ignored. Currently-defined types are described below.
    3rd BYTE Bitwise OR of flags. Unknown flags must be ignored. Currently-defined flags are described below.
    4th BYTE Major protocol version of the sending application. If the major protocol version of the receiving application does not match, the applications will not be able to communicate and the D-Bus connection must be disconnected. The major protocol version for this version of the specification is 1.
    1st UINT32 Length in bytes of the message body, starting from the end of the header. The header ends after its alignment padding to an 8-boundary.
    2nd UINT32 The serial of this message, used as a cookie by the sender to identify the reply corresponding to this request.
    ARRAY ofSTRUCT of (BYTE,VARIANT) An array of zero or more header fields where the byte is the field code, and the variant is the field value. The message type determines which fields are required.

    在訊息頭尾部的陣列包含了頭部域。

    訊息有四種類型:METHOD_CALLMETHOD_RETURNERROR, and SIGNAL

       a.METHOD_CALL(方法呼叫訊息)

          這類訊息會呼叫遠端物件的操作,這些訊息需要對映到物件的方法上去。方法呼叫訊息需要有MEMBER頭部域用以指明方法的名稱,此訊息還可能有一個INTERFACE域來給出介面,被呼叫的方法也是介面的一部分。在沒有INTERFACE域時,如果同一物件上的兩個介面有同名的方法名字,哪一個方法會被呼叫是沒有定義的。在這種具有二義性的環境裡,實現可以選擇返回一個錯誤。但是,如果方法名是獨一無二的,實現不能強制要求需要interface域。還包括一個PATH域,該域用以指明在哪個物件上呼叫該方法。如果此呼叫正在訊息匯流排中傳播,訊息也有一個DESTINATION域用以給出接收此訊息的連線名稱。當應用程式處理方法呼叫訊息時,它需要答覆,答覆由REPLY_SERIAL頭部域確定 ,此頭部域也表明了被答覆的方法呼叫的序列號。答覆型別有兩種,METHOD_RETURN和ERROR。

       b.METHOD_RETURN & ERROR

          答覆訊息的引數就是方法呼叫的返回值或"out parameters",如果答覆型別為ERROR,那麼會丟擲例外,方法呼叫失敗,此時沒有返回值提供。對於同一個方法呼叫傳送多個答覆是沒有意義的。 即使一個方法呼叫沒有返回值,一個METHOD_RETURN的答覆也是必須的,這樣呼叫者就能知道方法是否被成功地處理了。

    METHOD_RETURN 和 ERROR答覆訊息必須有REPLY_SERIAL頭部域。 

       d.SIGNAL

              訊號發射不象方法呼叫需要答覆。訊號發射只是簡單單一資訊型別SIGNAL。它必須有三個頭域,PATH給出傳送訊號的物件,加上INTERFACE和MEMBER給出訊號的全稱名字。INTERFACE頭部域在訊號中是必須的,儘管它在方法呼叫中是可選的。

  2. Bus Name 
    可以說是連線的名字,一個連線只有一個匯流排名字並且是惟一的,在此連線的整個生命週期,這個名字不變。匯流排名字是STRING型別的,意味著它必須是有效的UTF-8字串。有兩種作用不同的Bus Name,一個叫公共名(well-known names),還有一個叫唯一名(Unique Connection Name)。
  3. 物件 
    物件是處理訊息的一個例項。物件有一個或多個介面,在每個介面有一個或多個的方法,每個方法實現了具體的訊息處理。在一對一的通訊中,物件通過一個連線直接和另一個客戶端應用程式連線起來。在多對多的通訊中,物件通過一個連線和Dbus守護程序連線起來。物件有一個路徑用於指明該物件的存放位置,訊息傳遞時通過該路徑找到該物件。
  4. 介面 
    每個物件都有一個或者多個介面,一個介面就是多個方法和訊號的集合。dbus使用簡單的名稱空間字串來表示介面,如org.freedesktop.Introspectable。可以說dbus介面相當於C++中的純虛類。
  5. 方發和訊號 
    每個物件都有一些成員,兩種成員:方法(methods)和訊號(signals),在物件中,方法可以被呼叫。訊號會被廣播,感興趣的物件可以處理這個訊號,同時訊號中也可以帶有相關的資料。每一個方法或者訊號都可以用一個名字來命名,如”Frobate” 或者 “OnClicked”。
  6. 物件路徑 
    一個物件路徑是一個用於引用物件例項的名字,從概念上講,D-Bus資訊交換中每個參與者可能有任意數量的物件例項並且每個這樣的例項都有一個路徑,就象檔案系統一樣,一個應用中的物件例項形成一個層次樹。
  7. 服務 
    服務是 D-BUS 的最高層次抽象,它們的實現當前還在不斷髮展變化。應用程式可以通過一個匯流排來註冊一個服務,如果成功,則應用程式就已經 獲得 了那個服務。其他應用程式可以檢查在總線上是否已經存在一個特定的服務,如果沒有可以要求匯流排啟動它。
  8. dbus-daemon 
    dbus的後臺程式(守護程序),libdbus執行時會自動建立dbus-daemon程序。daemon就像一個路由器,將從傳送者連線得到的訊息轉發到由訊息中的接收者連線名指定的接收者連線中。
  9. 連線 
    連線是一個雙向的訊息傳遞通道。一個連線將物件和Dbus(dubs-daemon)或客戶端應用連線起來,連線支援非阻塞式的非同步訊息傳送和阻塞式的同步訊息傳送。訊息通過連線到達目的端後,連線會將掛起在該連線上的程序喚醒,由該程序將訊息取走。每個連線都有一個唯一的名字和可選的其他多個名字,用於在多對多通訊時指明訊息的傳送者和接收者
  10. 認證協議(Authentication Protocol)

    訊息流開始之前,兩個應用之間必須進行認證,簡單的純文字協議用來認證,即SASL檔案,相當簡單地直接從SASL規範對映過來。沒有使用訊息編碼,只是純文字資訊。

      二、執行機制

       客戶端應用是請求訊息的發起者。客戶端應用通過和自身的相連的一個連線將請求訊息傳送出去,也通過該連線接收回應的訊息、錯誤訊息、系統更新訊息等。在一對一的通訊中,請求訊息直接到達物件。在多對多的通訊中,請求訊息先到達Dbus,Dbus將訊息轉發到目的物件。每個連線使用bus name來標識的,bus name有兩種,一種是公共名(well-known Name),一種是唯一名(Unique Connection Name)。

      所有使用D-BUS的應用程式都包含一些物件,它們一般對映為GObject、QObject、C++物件、或者[[Python">Python</a>物件。當經由一個D-BUS連線收到一條訊息時,該訊息是被髮往一個物件而不是整個應用程式。這一點和我們常見的訊息機制是不太一樣的。這個物件其實很像C++中虛擬函式

      為了允許訊息能指定接收物件,還要提供引用物件的方法。但是這個引用一般實現為與應用程式相關的記憶體地址,因此無法在應用程式之間傳遞。為了解決這一問題,D-BUS為每個物件引入名字。這些名字看起來像是檔案系統路徑,例如一個物件可能叫做 “/org/kde/kspread/sheets/3/cells/4/5”。路徑名以容易閱讀為佳,沒有具體的硬性規定。

在dbus中呼叫一個方法包含了兩條訊息,程序A向程序B傳送方法呼叫訊息,程序B向程序A傳送應答訊息。所有的訊息都由daemon進行分派,每個呼叫的訊息都有一個不同的序列號,返回訊息包含這個序列號,以方便呼叫者匹配呼叫訊息與應答訊息。呼叫訊息包含一些引數,應答訊息可能包含錯誤標識,或者包含方法的返回資料。

        1.方法呼叫的一般流程: 
a.使用不同語言繫結的dbus高層介面,都提供了一些代理物件,呼叫其他程序裡面的遠端物件就像是在本地程序中的呼叫一樣。應用呼叫代理上的方法,代理將構造一個方法呼叫訊息給遠端的程序。 
b.在DBUS的底層介面中,應用需要自己構造方法呼叫訊息(method call message),而不能使用代理。 
c.方法呼叫訊息裡面的內容有:目的程序的bus name,方法的名字,方法的引數,目的程序的物件路徑,以及可選的介面名稱。 
d.方法呼叫訊息是傳送到bus daemon中的。 
e.bus daemon查詢目標的bus name,如果找到,就把這個方法傳送到該程序中,否則,daemon會產生錯誤訊息,作為應答訊息給傳送程序。 
f.目標程序解開訊息,在dbus底層介面中,會立即呼叫方法,然後傳送方法的應答訊息給daemon。在dbus高層介面中,會先檢測物件路徑,介面,方法名稱,然後把它轉換成對應的物件(如GObject,QT中的QObject等)的方法,然後再將應答結果轉換成應答訊息發給daemon。 
g.bus daemon接受到應答訊息,將把應答訊息直接發給發出呼叫訊息的程序。 
h.應答訊息中可以包容很多返回值,也可以標識一個錯誤發生,當使用繫結時,應答訊息將轉換為代理物件的返回值,或者進入異常。

bus daemon不對訊息重新排序,如果傳送了兩條訊息到同一個程序,他們將按照發送順序接受到。接受程序並需要按照順序發出應答訊息,例如在多執行緒中處理這些訊息,應答訊息的發出是沒有順序的。訊息都有一個序列號可以與應答訊息進行配對。

 

在dbus中一個訊號包含一條訊號訊息,一個程序發給多個程序。也就是說,訊號是單向的廣播。訊號可以包含一些引數,但是作為廣播,它是沒有返回值的。

訊號觸發者是不瞭解訊號接受者的,接受者向daemon註冊感興趣的訊號,註冊規則是”match rules”,記錄觸發者名字和訊號名字。daemon只向註冊了這個訊號的程序傳送訊號。

       2.訊號的一般流程如下: 
a.當使用dbus底層介面時,訊號需要應用自己建立和傳送到daemon,使用dbus高層介面時,可以使用相關物件進行傳送,如Glib裡面提供的訊號觸發機制。 
b.訊號包含的內容有:訊號的介面名稱,訊號名稱,傳送程序的bus name,以及其他引數。 
c.任何程序都可以依據”match rules”註冊相關的訊號,daemon有一張註冊的列表。 
d.daemon檢測訊號,決定哪些程序對這個訊號感興趣,然後把訊號傳送給這些程序。 
e.每個程序收到訊號後,如果是使用了dbus高層介面,可以選擇觸發代理物件上的訊號。如果是dbus底層介面,需要檢查傳送者名稱和訊號名稱,然後決定怎麼做。

     三、將訊息呼叫對映為本地API

      D-Bus的API可以把方法呼叫對映為特定程式語言(如C++)中的方法呼叫,或者把IDL中的方法呼叫對映為D-Bus訊息。

在這些方法呼叫中,方法的引數被標為"in"(指明是METHOD_CALL傳入引數)或者"out"(指明是METHOD_CALL的傳出引數),象CORBA的一些API具有"inout"引數,它表明該引數既傳入又傳出,即呼叫者傳入的值將會被修改。對映到D-Bus上,"inout"引數等價於"in"引數後跟著一個"out"引數,你不能在總線上傳引用,所以"inout"引數完全是幻想。如果一個方法具有0個或一個返回值,後跟著0個或多個引數,這裡的每個引數可能是"in", "out"或"inout",呼叫者通過按順序加上"in"或者"inout"引數來構造訊息。"out"引數不會出現在呼叫者的訊息裡。接收者構造出答覆,如果有返回值,則把第一個返回加到答覆上,然後按順序加上每個"out"或"inout"引數,"in"引數不會出現在答覆訊息裡。如果使用的語言中有例外錯誤答覆訊息正常情況下被對映為例外。在轉換本地API到D-Bus的API時,把D-Bus命名規範("FooBar")自動地對映到本地命名規範(fooBar/foo_bar)或許好一些。只要本地API是專門寫給D-Bus就行。這樣當寫一個物件實現且此實現將被匯出到總線上是最好的了。物件代理用於呼叫遠端D-Bus物件,而遠端D-Bus物件可能需要可以呼叫任何D-Bus方法的能力,因此像這樣魔術般的名字對映可能是個問題。

 

我們瞭解了什麼是D-BUS了也瞭解了他的基本原理。精彩的是D-BUS是一個完全的opensource軟體。那麼是時候來看看程式碼了

由於dbus是支援快平臺的,自然需要先考慮清楚我們想研究的目標平臺。下載了linux版和windows版,其他版本沒有下。

工作環境是windows,但是最初從官方網頁指引的地方下載了一個windbus,沒編譯過去,本來也是一般linux的東西在Windows下編譯總是一件痛苦的事情,要麼藉助其他第三方工具庫,要麼連程式碼都不一樣。如果需要把linux porting到windows有時候也是蠻麻煩的事情,比如彙編,但是還有很多純粹的體力勞動,比如編譯器支援的語法不一樣。為了能正常編譯linux版偶還特意裝了一個redhat 9的虛擬機器。配置就搞了半天,原因很可笑,僅僅是因為安裝好vmware新增的網絡卡是預設使用靜態IP的。很多公司都是不允許的。以前怎麼一點印象都沒有。下載了1.2.4版本,但是在linux下編譯沒成功,正在找原因的時候發現可以通過svn下載windbus(dbus的windows版)程式碼。找了一篇編譯日誌,稍加整理hoho編譯、執行一切ok。下面是整理出來的編譯dbus Windows版的步驟:

1.下載程式碼

通過TortoiseSVN:

https://windbus.svn.sourceforge.net/svnroot/windbus

2.下載並安裝cmake

http://www.cmake.org/files/v2.6/cmake-2.6.1-win32-x86.exe,安裝到<ProgramDir>\gnuwin32 
3.下載並安裝編譯庫

http://www.winkde.org/pub/kde/ports/win32/releases/stable/4.1.1/libxml2-2.6.32-1-lib.tar.bz2

http://www.winkde.org/pub/kde/ports/win32/releases/stable/4.1.1/libxml2-2.6.32-1-bin.tar.bz2

http://www.winkde.org/pub/kde/ports/win32/releases/stable/4.1.1/expat-2.0.1-bin.zip

http://www.winkde.org/pub/kde/ports/win32/releases/stable/4.1.1/expat-2.0.1-lib.zip

將以上庫都解壓至<ProgramDir>\gnuwin32目錄,把四個庫裡所有lib,bin目錄下的檔案都拷貝到<ProgramDir>\gnuwin32目錄下的bin和lib目錄 
3.使用cmake生成sln檔案

   a.將windbus\tags\1.2.4拷到一個新的目錄如:C:\winbus-1.2.4\source

   b.建立一個新的目錄,用於生成sln檔案以及其他需要的相關檔案和目錄。如:C:\winbus-1.2.4\compile

   c.開啟CMD,進入到C:\windbus-1.2.4\complile目錄,執行cmake -G "Visual Studio 8 2005" ..\windbus-1.2.4\source\cmake

   d.如果成功,complie目錄下將生成sln檔案,VS2005開啟此檔案即可進行編譯。

注意:VS2008 用cmake -G "Visual Studio 9 2008" ..\windbus-1.2.4\source\cmake 命令,其他版本請參見cmakehelp