1. 程式人生 > >dbus 之 dbus-glib

dbus 之 dbus-glib

應用程式A和訊息匯流排連線,這個連接獲取了一個眾所周知的公共名(記作連線A)。應用程式A中有物件A1提供了介面I1,介面I1有方法M1。應用程式B和訊息匯流排連線,要求呼叫連線A上物件A1的介面I1的方法M1。

在上一講的加法例子中,上面這段話可以例項化為:應用程式example-service和會話匯流排連線。這個連接獲取了一個眾所周知的公共名“org.fmddlmyy.Test”。應用程式example-servic中有物件“/TestObj”提供了介面“org.fmddlmyy.Test.Basic”,介面“org.fmddlmyy.Test.Basic”有方法“Add”。應用程式d-feet和會話匯流排連線,要求呼叫連線“org.fmddlmyy.Test”上物件“/TestObj”的介面“org.fmddlmyy.Test.Basic”的方法“Add”。

應用程式B呼叫應用程式A的方法,其實就是應用程式B嚮應用程式A傳送了一個型別為“method_call”的訊息。應用程式A通過一個型別為“method_retutn”的訊息將返回值發給應用程式B。我們簡單介紹一下D-Bus總線上的訊息。

1、D-Bus的訊息

上一講說過最基本的D-Bus協議是一對一的通訊協議。與直接使用socket不同,D-Bus是面向訊息的協議。 D-Bus的所有功能都是通過在連線上流動的訊息完成的。

1.1、訊息型別

D-Bus有四種類型的訊息:

  • method_call 方法呼叫
  • method_return 方法返回
  • error 錯誤
  • signal 訊號

前面介紹的遠端方法呼叫就用到了method_call和method_return訊息。顧名思義,在發生錯誤時會產生error訊息。如果把method_call看作打電話,那麼signal訊息就是來電了。後面還會詳細討論。

1.2、dbus-send和dbus-monitor

dbus提供了兩個小工具:dbus-send和dbus-monitor。我們可以用dbus-send傳送訊息。用dbus-monitor監視總線上流動的訊息。讓我們通過dbus-send傳送訊息來呼叫前面的Add方法,這時dbus-send充當了應用程式B。用dbus-monitor觀察呼叫過程中的訊息。

啟動example-service:

$ ./example-service 

在另一個控制檯啟動dbus-monitor:

$ dbus-monitor

dbus-monitor預設監視會話匯流排。執行:

$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.fmddlmyy.Test.Basic.Add int32:100 int32:999

輸出為:

method return sender=:1.21 -> dest=:1.22 reply_serial=2
   int32 1099

dbus-monitor的相關輸出包括:

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"
method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello
method call sender=:1.22 -> dest=org.fmddlmyy.Test path=/TestObj; interface=org.fmddlmyy.Test.Basic; member=Add
   int32 100
   int32 999
method return sender=:1.21 -> dest=:1.22 reply_serial=2
   int32 1099
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 ""

:1.22就是dbus-send在本次呼叫中與會話匯流排所建立連線的唯一名。:1.21是連線“org.fmddlmyy.Test”的唯一名。在以上輸出中我們可以看到:1.22向“org.fmddlmyy.Test”傳送method_call訊息,呼叫Add方法。 :1.21通過method_return訊息將呼叫結果發回:1.22。其它輸出資訊會在以後說明。

dbus-send的詳細用法可以參閱手冊。呼叫遠端方法的一般形式是:

$ dbus-send [--system | --session] --type=method_call --print-reply --dest=連線名 物件路徑 介面名.方法名 引數型別:引數值 引數型別:引數值

dbus-send支援的引數型別包括:string, int32, uint32, double, byte, boolean。

2、訊息匯流排的方法和訊號

2.1、概述

訊息匯流排是一個特殊的應用,它可以在與它連線的應用之間傳遞訊息。可以把訊息匯流排看作一臺路由器。正是通過訊息匯流排,D-Bus才在一對一的通訊協議基礎上實現了多對一和一對多的通訊。

訊息匯流排雖然有特殊的轉發功能,但訊息匯流排也還是一個應用。其它應用與訊息匯流排的通訊也是通過1.1節的基本訊息型別完成的。作為一個應用,訊息匯流排也提供了自己的介面,包括方法和訊號。

我們可以通過向連線“org.freedesktop.DBus ”上物件“/”傳送訊息來呼叫訊息匯流排提供的方法。事實上,應用程式正是通過這些方法連線到訊息總線上的其它應用,完成請求公共名等工作的。

2.2、清單

訊息匯流排物件支援第一講中提到的標準介面"org.freedesktop.DBus.Introspectable",我們可以呼叫org.freedesktop.DBus.Introspectable.Introspect方法檢視訊息匯流排物件支援的介面。例如:

$ dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.20 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus">
    <method name="Hello">
      <arg direction="out" type="s"/>
    </method>
    <method name="RequestName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="ReleaseName">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="StartServiceByName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="NameHasOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="b"/>
    </method>
    <method name="ListNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="ListActivatableNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="AddMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="RemoveMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="GetNameOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="s"/>
    </method>
    <method name="ListQueuedOwners">
      <arg direction="in" type="s"/>
      <arg direction="out" type="as"/>
    </method>
    <method name="GetConnectionUnixUser">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetConnectionUnixProcessID">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetConnectionSELinuxSecurityContext">
      <arg direction="in" type="s"/>
      <arg direction="out" type="ay"/>
    </method>
    <method name="ReloadConfig">
    </method>
    <method name="GetId">
      <arg direction="out" type="s"/>
    </method>
    <signal name="NameOwnerChanged">
      <arg type="s"/>
      <arg type="s"/>
      <arg type="s"/>
    </signal>
    <signal name="NameLost">
      <arg type="s"/>
    </signal>
    <signal name="NameAcquired">
      <arg type="s"/>
    </signal>
  </interface>
</node>
"

從輸出可以看到會話匯流排物件支援標準介面“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) 一個應用在通過訊息匯流排向其它應用發訊息前必須先呼叫Hello獲取自己這個連線的唯一名。返回值就是連線的唯一名。dbus沒有定義專門的切斷連線命令,關閉socket就是切斷連線。
在1.2節的dbus-monitor輸出中可以看到dbus-send呼叫訊息匯流排的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) 返回所有可以啟動的服務名。dbus支援按需啟動服務,即根據應用程式的請求啟動服務。
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) 按名稱啟動服務。引數flags暫未使用。返回值ret_val定義如下:
1 服務被成功啟動
2 已經有連線擁有要啟動的服務名
org.freedesktop.DBus.GetNameOwner (in STRING name, out STRING unique_connection_name) 返回擁有指定公眾名的連線的唯一名。
org.freedesktop.DBus.GetConnectionUnixUser (in STRING connection_name, out UINT32 unix_user_id) 返回指定連線對應的伺服器程序的Unix使用者id。
org.freedesktop.DBus.AddMatch (in STRING rule) 為當前連線增加匹配規則。
org.freedesktop.DBus.RemoveMatch (in STRING rule) 為當前連線去掉指定匹配規則。
org.freedesktop.DBus.GetId (out STRING id) 返回訊息匯流排的ID。這個ID在訊息匯流排的生命期內是唯一的。

介面“org.freedesktop.DBus”的3個訊號是:

org.freedesktop.DBus.NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner) 指定名稱的擁有者發生了變化。
org.freedesktop.DBus.NameLost (STRING name) 通知應用失去了指定名稱的擁有權。
org.freedesktop.DBus.NameAcquired (STRING name) 通知應用獲得了指定名稱的擁有權。

2.3、練習

讓我們來試試訊息匯流排提供的方法。

2.3.1、從ListName到d-feet的基本邏輯

用dbus-send呼叫:

$ dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.23 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string "org.freedesktop.Notifications"
      string "org.freedesktop.Tracker"
      string "org.freedesktop.PowerManagement"
      string ":1.7"
      string ":1.8"
      string "org.gnome.ScreenSaver"
      string ":1.9"
      string ":1.10"
      string ":1.22"
      string ":1.11"
      string "org.gnome.GnomeVFS.Daemon"
      string ":1.23"
      string ":1.12"
      string ":1.13"
      string ":1.0"
      string ":1.14"
      string ":1.1"
      string ":1.15"
      string ":1.2"
      string ":1.16"
      string ":1.3"
      string "org.gnome.GkbdConfigRegistry"
      string ":1.4"
      string "org.fmddlmyy.Test"
      string ":1.5"
      string "org.gnome.SettingsDaemon"
      string ":1.6"
   ]

這是會話匯流排當前已連線的連線名。在d-feet視窗的左側視窗顯示的就是ListNames返回的連線名。聰明的讀者也許已經想到使用訊息匯流排的“org.freedesktop.DBus.ListNames”方法和各連線的“org.freedesktop.DBus.Introspectable.Introspect”,我們就可以像d-feet一樣檢視總線上所有連線的所有物件的所有介面的所有方法和訊號。

你的想法很好。但有一個問題,我們必須對連線中的物件呼叫“org.freedesktop.DBus.Introspectable.Introspect”方法。 ListNames只列出了連線名,我們怎麼獲取連線中的物件路徑呢?

答案很簡單,如果我們不知道物件路徑就從根目錄開始吧。連線中的物件是按照樹型結構組織的。我們遍歷連線的物件樹就可以找到所有的物件。呼叫物件的“org.freedesktop.DBus.Introspectable.Introspect”方法就可以檢視物件的所有介面的所有方法和訊號。例如:假設我們不知道連線"org.fmddlmyy.Test"裡有什麼物件,我們可以對根物件"/"執行:

$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test / org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.22 -> dest=:1.25 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="TestObj"/>
</node>
"

"org.fmddlmyy.Test"的物件樹的根節點只有一個子節點"TestObj",再檢視"/TestObj":

$ dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.22 -> dest=:1.26 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
      <arg name="interface" direction="in" type="s"/>
      <arg name="propname" direction="in" type="s"/>
      <arg name="value" direction="out" type="v"/>
    </method>
    <method name="Set">
      <arg name="interface" direction="in" type="s"/>
      <arg name="propname" direction="in" type="s"/>
      <arg name="value" direction="in" type="v"/>
    </method>
    <method name="GetAll">
      <arg name="interface" direction="in" type="s"/>
      <arg name="props" direction="out" type="a{sv}"/>
    </method>
  </interface>
  <interface name="org.fmddlmyy.Test.Basic">
    <method name="Add">
      <arg name="arg0" type="i" direction="in"/>
      <arg name="arg1" type="i" direction="in"/>
      <arg name="ret" type="i" direction="out"/>
    </method>
  </interface>
</node>
"

作為一個練習,讓我們來檢視系統匯流排的上的bluez介面。執行:

$ dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.30 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string ":1.7"
      string ":1.8"
      string ":1.9"
      string "org.freedesktop.SystemToolsBackends"
      string ":1.30"
      string "org.freedesktop.NetworkManagerInfo"
      string ":1.20"
      string "org.freedesktop.Avahi"
      string ":1.21"
      string "org.bluez"
      string ":1.22"
      string "org.freedesktop.NetworkManager"
      string "org.freedesktop.ConsoleKit"
      string ":1.23"
      string "com.redhat.dhcp"
      string ":1.13"
      string ":1.0"
      string ":1.14"
      string ":1.1"
      string ":1.15"
      string ":1.2"
      string "org.freedesktop.Hal"
      string "com.redhat.NewPrinterNotification"
      string ":1.16"
      string ":1.3"
      string ":1.17"
      string ":1.4"
      string ":1.18"
      string ":1.5"
      string ":1.19"
      string ":1.6"
   ]

我們看到連線"org.bluez"。檢視它的根物件:

$ dbus-send --system --type=method_call --print-reply --dest=org.bluez / org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.7 -> dest=:1.31 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="org"/>
</node>
"

接著查物件"/org":

$ dbus-send --system --type=method_call --print-reply --dest=org.bluez /org org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.7 -> dest=:1.32 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="bluez"/>
</node>
"

接著查物件"/org/bluez":

$ dbus-send --system --type=method_call --print-reply --dest=org.bluez /org/bluez org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.7 -> dest=:1.33 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/org/bluez">
        <interface name="org.bluez.Manager">
                <method name="InterfaceVersion">
                        <arg type="u" direction="out"/>
                </method>
                <method name="DefaultAdapter">
                        <arg type="s" direction="out"/>
                </method>
                <method name="FindAdapter">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="out"/>
                </method>
                <method name="ListAdapters">
                        <arg type="as" direction="out"/>
                </method>
                <method name="FindService">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="out"/>
                </method>
                <method name="ListServices">
                        <arg type="as" direction="out"/>
                </method>
                <method name="ActivateService">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="out"/>
                </method>
                <signal name="AdapterAdded">
                        <arg type="s"/>
                </signal>
                <signal name="AdapterRemoved">
                        <arg type="s"/>
                </signal>
                <signal name="DefaultAdapterChanged">
                        <arg type="s"/>
                </signal>
                <signal name="ServiceAdded">
                        <arg type="s"/>
                </signal>
                <signal name="ServiceRemoved">
                        <arg type="s"/>
                </signal>
        </interface>
        <interface name="org.bluez.Database">
                <method name="AddServiceRecord">
                        <arg type="ay" direction="in"/>
                        <arg type="u" direction="out"/>
                </method>
                <method name="AddServiceRecordFromXML">
                        <arg type="s" direction="in"/>
                        <arg type="u" direction="out"/>
                </method>
                <method name="UpdateServiceRecord">
                        <arg type="u" direction="in"/>
                        <arg type="ay" direction="in"/>
                </method>
                <method name="UpdateServiceRecordFromXML">
                        <arg type="u" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="RemoveServiceRecord">
                        <arg type="u" direction="in"/>
                </method>
                <method name="RegisterService">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterService">
                        <arg type="s" direction="in"/>
                </method>
                <method name="RequestAuthorization">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="CancelAuthorizationRequest">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
        </interface>
        <interface name="org.bluez.Security">
                <method name="RegisterDefaultPasskeyAgent">
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterDefaultPasskeyAgent">
                        <arg type="s" direction="in"/>
                </method>
                <method name="RegisterPasskeyAgent">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterPasskeyAgent">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="RegisterDefaultAuthorizationAgent">
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterDefaultAuthorizationAgent">
                        <arg type="s" direction="in"/>
                </method>
        </interface>
        <node name="service_audio"/>
        <node name="service_input"/>
        <node name="service_network"/>
        <node name="service_serial"/>
</node>
"

我們看到了物件"/org/bluez"的所有介面。物件"/org/bluez"還有子節點"service_audio"、"service_input"、"service_network"和"service_serial"。必要時我們可以接著查下去。d-feet的基本邏輯就是這樣。後面我們會自己實現一個dteeth。dteeth是命令列程式,可以遍歷指定連線的物件樹,列出所有物件的所有介面的方法和訊號。