D-Bus體系系列

本文轉自Roboter's blog

D-Bus Tutorial進行了一些翻譯加上自己的一些理解。

有很多種IPC或者網絡通信系統,如:CORBA, DCE, DCOM, DCOP, XML-RPC, SOAP, MBUS, Internet Communications Engine (ICE)等等,可能會有數百種,dbus的目的主要是下面兩點:
1.在同一個桌面會話中,進行桌面應用程序之間的通訊
2.桌面程序與內核或者守護進程的通信。

Dbus是一套進程通信體系,它有以下幾層:
1.libdbus庫,提供給各個應用程序調用,使應用程序具有通信和數據交換的能力,兩個應用程序可以直接進行通信,就像是一條socket通道,兩個程序之間建立通道之後,就可以通訊了。
2.消息守護進程,在libdbus的基礎上創建,可以管理多個應用程序之間的通信。每個應用程序都和消息守護進程建立dbus的鏈接,然後由消息守護進程進行消息的分派。
3.各種包裝庫,有libdbus-glib,libdbus-qt等等,目的是將dbus的底層api進行一下封裝。

下面有一張圖可以很方便說明dbus的體系結構。
dbus

dbus中的消息由一個消息頭(標識是哪一種消息)和消息數據組成,比socket的流式數據更方便一些。bus daemon 就像是一個路由器,與各個應用程序進行連接,分派這些消息。bus daemon 在一臺機器上有多個實例,第一個實例是全局的實例,類似於sendmail和或者apache,這個實例有很嚴格的安全限制,只接受一些特定的系統消息, 用於系統通信。其他bus daemon是一些會話,用於用戶登錄之後,在當前會話(session)中進行的通訊。系統的bus daemon 和會話的bus daemon 是分開的,彼此不會互相影響,會話bus daemon 不會去調用系統的bus daemon 。

Native Objects and Object Paths
在不同的編程語言中,都定義了一些“對象”,如java中的java.lang.Object,GLIB中的GObject,QT中的QObject等 等。D-BUS的底層接口,和libdbus API相關,是沒有這些對象的概念的,它提供的是一種叫對象路徑(object path),用於讓高層接口綁定到各個對象中去,允許遠端應用程序指向它們。object path就像是一個文件路徑,可以叫做/org/kde/kspread/sheets/3/cells/4/5等。

Methods and Signals
每個對象都有一些成員,兩種成員:方法(methods)和信號(signals),在對象中,方法可以被調用。信號會被廣播,感興趣的對象可以處理這個 信號,同時信號中也可以帶有相關的數據。每一個方法或者信號都可以用一個名字來命名,如”Frobate” 或者 “OnClicked”。

Interfaces
每個對象都有一個或者多個接口,一個接口就是多個方法和信號的集合。dbus使用簡單的命名空間字符串來表示接口,如org.freedesktop.Introspectable。可以說dbus接口相當於C++中的純虛類。

Proxies
代理對象用於模擬在另外的進程中的遠端對象,代理對象像是一個正常的普通對象。d-bus的底層接口必須手動創建方法調用的消息,然後發送,同時必須手動 接受和處理返回的消息。高層接口可以使用代理來替換這些,當調用代理對象的方法時,代理內部會轉換成dbus的方法調用,等待消息返回,對返回結果解包, 返回給相應的方法。可以看看下面的例子,使用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
當一個應用程序連接上bus daemon時,daemon會分配一個唯一的名字給它。以冒號(:)開始,這些名字在daemon的生命週期中是不會改變的,可以認爲這些名字就是一個 IP地址。當這個名字映射到應用程序的連接上時,應用程序可以說擁有這個名字。同時應用可以聲明額外的容易理解的名字,比如可以取一個名字 com.mycompany.TextEditor,可以認爲這些名字就是一個域名。其他應用程序可以往這個名字發送消息,執行各種方法。

名字還有第二個重要的用途,可以用於跟蹤應用程序的生命週期。當應用退出(或者崩潰)時,與bus的連接將被OS內核關掉,bus將會發送通知,告訴剩餘的應用程序,該程序已經丟失了它的名字。名字還可以檢測應用是否已經啓動,這往往用於只能啓動一個實例的應用。

Addresses
使用d-bus的應用程序既可以是server也可以是client,server監聽到來的連接,client連接到server,一旦連接建立,消息 就可以流轉。如果使用dbus daemon,所有的應用程序都是client,daemon監聽所有的連接,應用程序初始化連接到daemon。

dbus地址指明server將要監聽的地方,client將要連接的地方,例如,地址:unix:path=/tmp/abcdef表明 server將在/tmp/abcdef路徑下監聽unix域的socket,client也將連接到這個socket。一個地址也可以指明是TCP /IP的socket,或者是其他的。

當使用bus daemon時,libdbus會從環境變量中(DBUS_SESSION_BUS_ADDRESS)自動認識“會話daemon”的地址。如果是系統 daemon,它會檢查指定的socket路徑獲得地址,也可以使用環境變量(DBUS_SESSION_BUS_ADDRESS)進行設定。

當dbus中不使用daemon時,需要定義哪一個應用是server,哪一個應用是client,同時要指明server的地址,這不是很通常的做法。

Big Conceptual Picture
要在指定的對象中調用指定的方法,需要知道的參數如下:
Address -> [Bus Name] -> Path -> Interface -> Method
bus name是可選的,除非是希望把消息送到特定的應用中才需要。interface也是可選的,有一些歷史原因,DCOP不需要指定接口,因爲DCOP在同一個對象中禁止同名的方法。

Messages - Behind the Scenes
如果使用dbus的高層接口,就可以不用直接操作這些消息。DBUS有四種類型的消息:
1.方法調用(method call) 在對象上執行一個方法
2.方法返回(method return)返回方法執行的結果
3.錯誤(error)調用方法產生的異常
4.信號(signal)通知指定的信號發生了,可以想象成“事件”。

要執行 D-BUS 對象的方法,需要向對象發送一個方法調用消息。它將完成一些處理並返回一個方法返回消息或者錯誤消息。信號的不同之處在於它們不返回任何內容:既沒有“信號返回”消息,也沒有任何類型的錯誤消息。

每個消息都有一個消息頭,包含多個字段,有一個消息體,包含多個參數。可以認爲消息頭是消息的路由信息,消息體作爲一個載體。消息頭裏面的字段包含 發送的bus name,目標bus name,方法或者信號名字等,同時消息頭裏面定義的字段類型規定了消息體裏面的數據格式。例如:字符“i”代表了”32-bit integer”,“ii”就代表了消息體裏面有兩個”32-bit integer”。

Calling a Method - Behind the Scenes
在dbus中調用一個方法包含了兩條消息,進程A向進程B發送方法調用消息,進程B向進程A發送應答消息。所有的消息都由daemon進行分派,每個調用 的消息都有一個不同的序列號,返回消息包含這個序列號,以方便調用者匹配調用消息與應答消息。調用消息包含一些參數,應答消息可能包含錯誤標識,或者包含 方法的返回數據。

方法調用的一般流程:
1.使用不同語言綁定的dbus高層接口,都提供了一些代理對象,調用其他進程裏面的遠端對象就像是在本地進程中的調用一樣。應用調用代理上的方法,代理將構造一個方法調用消息給遠端的進程。
2.在DBUS的底層接口中,應用需要自己構造方法調用消息(method call message),而不能使用代理。
3.方法調用消息裏面的內容有:目的進程的bus name,方法的名字,方法的參數,目的進程的對象路徑,以及可選的接口名稱。
4.方法調用消息是發送到bus daemon中的。
5.bus daemon查找目標的bus name,如果找到,就把這個方法發送到該進程中,否則,daemon會產生錯誤消息,作爲應答消息給發送進程。
6.目標進程解開消息,在dbus底層接口中,會立即調用方法,然後發送方法的應答消息給daemon。在dbus高層接口中,會先檢測對象路徑,接口, 方法名稱,然後把它轉換成對應的對象(如GObject,QT中的QObject等)的方法,然後再將應答結果轉換成應答消息發給daemon。
7.bus daemon接受到應答消息,將把應答消息直接發給發出調用消息的進程。
8.應答消息中可以包容很多返回值,也可以標識一個錯誤發生,當使用綁定時,應答消息將轉換爲代理對象的返回值,或者進入異常。

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

Emitting a Signal - Behind the Scenes
在dbus中一個信號包含一條信號消息,一個進程發給多個進程。也就是說,信號是單向的廣播。信號可以包含一些參數,但是作爲廣播,它是沒有返回值的。

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

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

 

 

Glib綁定接口在"dbus/dbus-glib.h"頭文件中定義。
dbus和glib的數據類型映射如下:

D-Bus basic type GType Free function Notes
BYTE G_TYPE_UCHAR    
BOOLEAN G_TYPE_BOOLEAN    
INT16 G_TYPE_INT   Will be changed to a G_TYPE_INT16 once
GLib has it
UINT16 G_TYPE_UINT   Will be changed to a G_TYPE_UINT16 once
GLib has it
INT32 G_TYPE_INT   Will be changed to a G_TYPE_INT32 once
GLib has it
UINT32 G_TYPE_UINT   Will be changed to a G_TYPE_UINT32 once
GLib has it
INT64 G_TYPE_GINT64    
UINT64 G_TYPE_GUINT64    
DOUBLE G_TYPE_DOUBLE    
STRING G_TYPE_STRING g_free  
OBJECT_PATH DBUS_TYPE_G_PROXY g_object_unref The returned proxy does not have an interface set; use
dbus_g_proxy_set_interface to invoke methods

Container type mappings
dbus數據也有包容器類型,像DBUS_TYPE_ARRAY 和 DBUS_TYPE_STRUCT,dbus的數據類型可以是嵌套的,如有一個數組,內容是字符串的數組集合。

但是,並不是所有的類型都有普通的使用,DBUS_TYPE_STRUCT應該可以包容非基本類型的數據類型。glib綁定嘗試使用比較明顯的方式進行聲明。

D-Bus type signature Description GType C typedef Free function Notes
as Array of strings G_TYPE_STRV char ** g_strfreev  
v Generic value container G_TYPE_VALUE GValue * g_value_unset The calling conventions for values expect that method callers have
allocated return values; see below.

同時定義了新的數組類型集合。

D-Bus type signature Description GType C typedef Free function Notes
ay Array of bytes DBUS_TYPE_G_BYTE_ARRAY GArray * g_array_free  
au Array of uint DBUS_TYPE_G_UINT_ARRAY GArray * g_array_free  
ai Array of int DBUS_TYPE_G_INT_ARRAY GArray * g_array_free  
ax Array of int64 DBUS_TYPE_G_INT64_ARRAY GArray * g_array_free  
at Array of uint64 DBUS_TYPE_G_UINT64_ARRAY GArray * g_array_free  
ad Array of double DBUS_TYPE_G_DOUBLE_ARRAY GArray * g_array_free  
ab Array of boolean DBUS_TYPE_G_BOOLEAN_ARRAY GArray * g_array_free  

定義了字典類型

D-Bus type signature Description GType C typedef Free function Notes
a{ss} Dictionary mapping strings to strings DBUS_TYPE_G_STRING_STRING_HASHTABLE GHashTable * g_hash_table_destroy  

client端編寫

我們的程序在使用dbus的時候,首先需要連接上dbus,使用dbus_g_bus_get獲得dbus連接。然後可以創建代理對象。

需要調用方法的時候,可以有兩種方式:1.同步調用,使用dbus_g_proxy_call發送方法請求到遠端對象,dbus會阻塞等待遠端對象 的迴應,輸出參數裏將會帶有相應的迴應數據,以G_TYPE_INVALID作爲終止符。2.異步調用,使用 dbus_g_proxy_begin_call,它將返回一個DBusGPendingCall對象,可以使用 dbus_g_pending_call_set_notify連接到自己的處理函授中。

可以使用dbus_g_proxy_add_signal 和 dbus_g_proxy_connect_signal來連接信號,dbus_g_proxy_add_signal用來聲明信號處理函數,屬於必須被 調用的接口,dbus_g_proxy_connect_signal可以調用多次。

Generated Bindings
使用內置的xml文件,可以很方便地自動創建出易於使用的dbus代理對象。如下的一個xml文件描述了了一個方法:


<?xml version="1.0" encoding="UTF-8" ?>
<node name="/com/example/MyObject">
<interface name="com.example.MyObject">
<method name="ManyArgs">
<arg type="u" name="x" direction="in" />
<arg type="s" name="str" direction="in" />
<arg type="d" name="trouble" direction="in" />
<arg type="d" name="d_ret" direction="out" />
<arg type="s" name="str_ret" direction="out" />
</method >
</interface >
</node >

“in”標識輸入參數,“out”標識輸出參數。
使用dbus-binding-tool工具來生成頭文件,如dbus-binding-tool –mode=glib-client my-object.xml > my-object-bindings.h,會產生如下的內聯函數原型:

/* This is a blocking call */
gboolean
com_example_MyObject_many_args (DBusGProxy *proxy, const guint IN_x,
const char * IN_str, const gdouble IN_trouble,
gdouble* OUT_d_ret, char ** OUT_str_ret,
GError **error);

/* This is a non-blocking call */
DBusGProxyCall*
com_example_MyObject_many_args_async (DBusGProxy *proxy, const guint IN_x,
const char * IN_str, const gdouble IN_trouble,
com_example_MyObject_many_args_reply callback,
gpointer userdata);

/* This is the typedef for the non-blocking callback */
typedef void
(*com_example_MyObject_many_args_reply)
(DBusGProxy *proxy, gdouble OUT_d_ret, char * OUT_str_ret,
GError *error, gpointer userdata);

所有函數的第一個參數都是DBusGProxy對象,一般是使用dbus_g_proxy_new_*函數創建出來的。客戶端發送方法請求可以增加 標記,目前只有org.freedesktop.DBus.GLib.NoReply標記,dbus可以不要回應消息,沒有“out”參數,這樣運算速度 會快一點。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章