消息匯流排和活動系統滲透到Linux桌面

火星人 @ 2014-03-26 , reply:0
←手機掃碼閱讀


  D-BUS 是一個大有前途的消息匯流排和活動系統,正開始深入地滲透到 Linux® 桌面之中。了解創建它的原因、它的用途以及發展前景。
  
  D-BUS 本質上是 進程間通信(inter-process communication)(IPC)的一個實現。不過,有一些特性使得 D-BUS 遠遠不是「只是另一個 IPC 實現」。有很多不同的 IPC 實現,因為每一個都定位於解決特定的明確定義的問題。CORBA 是用於面向對象編程中複雜的 IPC 的一個強大的解決方案。DCOP 是一個較輕量級的 IPC 框架,功能較少,但是可以很好地集成到 K 桌面環境中。SOAP 和 XML-RPC 設計用於 Web 服務,因而使用 HTTP 作為其傳輸協議。D-BUS 設計用於桌面應用程序和 OS 通信。
  
  桌面應用程序通信
  典型的桌面都會有多個應用程序在運行,而且,它們經常需要彼此進行通信。DCOP 是一個用於 KDE 的解決方案,但是它依賴於 Qt,所以不能用於其他桌面環境之中。類似的,Bonobo 是一個用於 GNOME 的解決方案,但是非常笨重,因為它是基於 CORBA 的。它還依賴於 GObject,所以也不能用於 GNOME 之外。 D-BUS 的目標是將 DCOP 和 Bonobo 替換為簡單的 IPC,並集成這兩種桌面環境。由於儘可能地減少了 D-BUS 所需的依賴,所以其他可能會使用 D-BUS 的應用程序不用擔心引入過多依賴。
  
  桌面/操作系統通信
  術語「操作系統」在這裡不僅包括內核,還包括系統後台進程。例如,通過使用 D-BUS 的 udev(Linux 2.6 中取代 devfs 的,提供動態 /dev 目錄),當設備(比如一個 USB 照相機)插入時會發放出一個信號。這樣可以更緊密地將硬體集成到桌面中,從而改善用戶體驗。
  
  D-BUS 特性
  D-BUS 有一些有趣的特性,使其像是一個非常有前途的選擇。
  
  協議是低延遲而且低開銷的,設計得小而高效,以便最小化傳送的往返時間。另外,協議是二進位的,而不是文本的,這樣就排除了費時的序列化過程。由於只面向本地機器處理的使用情形,所以所有的消息都以其自然位元組次序發送。位元組次序在每個消息中聲明,所以如果一個 D-BUS 消息通過網路傳輸到遠程的主機,它仍可以被正確地識別出來。
  
  從開發者的角度來看,D-BUS 是易於使用的。有線協議容易理解,客戶機程序庫以直觀的方式對其進行包裝。
  
  程序庫還設計用於為其他系統所包裝。預期,GNOME 將使用 GObject 創建包裝 D-BUS 的包裝器(實際上這些已經部分存在了,將 D-BUS 集成入它們的事件循環),KDE 將使用 Qt 創建類似的包裝器。由於 Python 具有面向對象特性和靈活的類型,已經有了具備類似介面的 Python 包裝器。
  
  最後,D-BUS 正在 freedesktop.org 的保護下進行開發,在那裡,來自 GNOME、KDE 以及其他組織的對此感興趣的成員參與了設計與實現。
  
  D-BUS 的內部工作方式
  典型的 D-BUS 設置將由幾個匯流排構成。將有一個持久的 系統匯流排(system bus),它在引導時就會啟動。這個匯流排由操作系統和後台進程使用,安全性非常好,以使得任意的應用程序不能欺騙系統事件。還將有很多 會話匯流排(session buses),這些匯流排當用戶登錄后啟動,屬於那個用戶私有。它是用戶的應用程序用來通信的一個會話匯流排。當然,如果一個應用程序需要接收來自系統匯流排的消息,它不如直接連接到系統匯流排 —— 不過,它可以發送的消息將是受限的。
  
  一旦應用程序連接到了一個匯流排,它們就必須通過添加 匹配器(matchers) 來聲明它們希望收到哪種消息。匹配器為可以基於介面、對象路徑和方法進行接收的消息指定一組規則(見后)。這樣就使得應用程序可以集中精力去處理它們想處理的內容,以實現消息的高效路由,並保持匯流排上消息的預期數量,以使得不會因為這些消息導致所有應用程序的性能下降並變得很慢。
  
  對象
  本質上,D-BUS 是一個對等(peer-to-peer)的協議 —— 每個消息都有一個源和一個目的。這些地址被指定為 對象路徑。概念上,所有使用 D-BUS 的應用程序都包括一組 對象,消息發送到或者發送自特定對象 —— 不是應用程序 —— 這些對象由對象路徑來標識。
  
  另外,每個對象都可以支持一個或多個 介面(interfaces)。這些介面看起來類似於 Java 中的介面或者 C++ 中的純粹的虛類(pure virtual classes)。不過,沒有選項來檢查對象是否實現了它們所聲明的介面,而且也沒有辦法可以調查對象內部以使列出其支持的介面。介面用於名稱空間和方法名稱,因此一個單獨的對象可以有名稱相同而介面不同的多個方法。
  
  消息
  在 D-BUS 中有四種類型的消息:方法調用(method calls)、方法返回(method returns)、信號(signals)和錯誤(errors)。要執行 D-BUS 對象的方法,您需要向對象發送一個方法調用消息。它將完成一些處理並返回一個方法返回消息或者錯誤消息。信號的不同之處在於它們不返回任何內容:既沒有「信號返回」消息,也沒有任何類型的錯誤消息。
  
  消息也可以有任意的參數。參數是強類型的,類型的範圍是從基本的非派生類型(布爾(booleans)、位元組(bytes)、整型(integers))到高層次數據結構(字元串(strings)、數組( arrays)和字典(dictionaries))。
  
  服務
  服務(Services) 是 D-BUS 的最高層次抽象,它們的實現當前還在不斷發展變化。應用程序可以通過一個匯流排來註冊一個服務,如果成功,則應用程序就已經 獲得 了那個服務。其他應用程序可以檢查在匯流排上是否已經存在一個特定的服務,如果沒有可以要求匯流排啟動它。服務抽象的細節 —— 尤其是服務活化 —— 當前正處於發展之中,應該會有變化。
  
  用例
  儘管 D-BUS 相對較新,但是卻迅速地得到了採用。如前所述,可以構建具有 D-BUS 支持的 udev 以使得當熱插拔(hot-plug)設備時它可以發送一個信號。任何應用程序都可以偵聽這些事件併當接收到這些事件時執行動作。例如,gnome-volume-manager 可以檢測到 USB 存儲棒的插入並自動掛載它;或者,當插入一個數碼相機時它可以自動下載照片。
  
  一個更為有趣但很不實用的例子是 Jamboree 和 Ringaling 的結合。Jamboree 是一個簡單的音樂播放器,它具有 D-BUS 介面,以使得它可以被告知播放、到下一首歌、改變音量等等。Ringaling 是一個小程序,它打開 /dev/ttyS0(一個串列埠)並觀察接收到的內容。當 Ringaling 發現文本「RING」時,就通過 D-BUS 告知 Jamboree 減小音量。最終的結果是,如果您的計算機上插入了一個數據機,而且電話鈴響,則音樂音量就會為您減小。這 正是計算機所追求的!
  
  代碼示例
  現在,讓我們來接觸一些使用 D-BUS 代碼的示例。
  
  dbus-ping-send.c 每秒通過會話匯流排發送一個參數為字元串「Ping!」的信號。我使用 Glib 來管理匯流排,以使得我不需要自己來處理匯流排的連接細節。
  
  清單 1. dbus-ping-send.c
  
  
QUOTE:
#include
  #include
  
  static gboolean send_ping (DBusConnection *bus);
  
  int
  main (int argc, char **argv)
  {
   GMainLoop *loop;
   DBusConnection *bus;
   DBusError error;
  
   /* Create a new event loop to run in */
   loop = g_main_loop_new (NULL, FALSE);
  
   /* Get a connection to the session bus */
   dbus_error_init (&error);
   bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
   if (!bus) {
    g_warning ("Failed to connect to the D-BUS daemon: %s", error.message);
    dbus_error_free (&error);
    return 1;
   }
  
   /* Set up this connection to work in a GLib event loop */
   dbus_connection_setup_with_g_main (bus, NULL);
   /* Every second call send_ping() with the bus as an argument*/
   g_timeout_add (1000, (GSourceFunc)send_ping, bus);
  
   /* Start the event loop */
   g_main_loop_run (loop);
   return 0;
  }
  
  static gboolean
  send_ping (DBusConnection *bus)
  {
   DBusMessage *message;
  
   /* Create a new signal "Ping" on the "com.burtonini.dbus.Signal" interface,
    * from the object "/com/burtonini/dbus/ping". */
   message = dbus_message_new_signal ("/com/burtonini/dbus/ping",
                     "com.burtonini.dbus.Signal", "Ping");
   /* Append the string "Ping!" to the signal */
   dbus_message_append_args (message,
                DBUS_TYPE_STRING, "Ping!",
                DBUS_TYPE_INVALID);
   /* Send the signal */
   dbus_connection_send (bus, message, NULL);
   /* Free the signal now we have finished with it */
   dbus_message_unref (message);
   /* Tell the user we send a signal */
   g_print("Ping!\n");
   /* Return TRUE to tell the event loop we want to be called again */
   return TRUE;
  }

  
  main 函數創建一個 GLib 事件循環,獲得會話匯流排的一個連接,並將 D-BUS 事件處理集成到 Glib 事件循環之中。然後它創建了一個名為 send_ping 間隔為一秒的計時器,並啟動事件循環。
  
  send_ping 構造一個來自於對象路徑 /com/burtonini/dbus/ping 和介面 com.burtonini.dbus.Signal 的新的 Ping 信號。然後,字元串 「Ping!」作為參數添加到信號中並通過匯流排發送。在標準輸出中會列印一條消息以讓用戶知道發送了一個信號。
  
  當然,不應該向匯流排發送了信號而沒有任何程序在偵聽它們……於是我們需要:
  
  清單 2. dbus-ping-listen.c
  
  
QUOTE:
#include
  #include
  
  static DBusHandlerResult signal_filter
     (DBusConnection *connection, DBusMessage *message, void *user_data);
  int
  main (int argc, char **argv)
  {
   GMainLoop *loop;
   DBusConnection *bus;
   DBusError error;
  
   loop = g_main_loop_new (NULL, FALSE);
  
   dbus_error_init (&error);
   bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
   if (!bus) {
    g_warning ("Failed to connect to the D-BUS daemon: %s", error.message);
    dbus_error_free (&error);
    return 1;
   }
   dbus_connection_setup_with_g_main (bus, NULL);
  
   /* listening to messages from all objects as no path is specified */
   dbus_bus_add_match (bus, "type='signal',interface='com.burtonini.dbus.Signal'");
   dbus_connection_add_filter (bus, signal_filter, loop, NULL);
  
   g_main_loop_run (loop);
   return 0;
  }
  
  static DBusHandlerResult
  signal_filter (DBusConnection *connection, DBusMessage *message, void *user_data)
  {
   /* User data is the event loop we are running in */
   GMainLoop *loop = user_data;
  
   /* A signal from the bus saying we are about to be disconnected */
   if (dbus_message_is_signal
      (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")) {
    /* Tell the main loop to quit */
    g_main_loop_quit (loop);
    /* We have handled this message, don't pass it on */
    return DBUS_HANDLER_RESULT_HANDLED;
   }
   /* A Ping signal on the com.burtonini.dbus.Signal interface */
   else if (dbus_message_is_signal (message, "com.burtonini.dbus.Signal", "Ping")) {
    DBusError error;
    char *s;
    dbus_error_init (&error);
    if (dbus_message_get_args
      (message, &error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
     g_print("Ping received: %s\n", s);
     dbus_free (s);
    } else {
     g_print("Ping received, but error getting message: %s\n", error.message);
     dbus_error_free (&error);
    }
    return DBUS_HANDLER_RESULT_HANDLED;
   }
   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }

  
  這個程序偵聽 dbus-ping-send.c 正在發出的信號。main 函數和前面一樣啟動,創建一個到匯流排的連接。然後它聲明願意當具有 com.burtonini.dbus.Signal 介面的信號被發送時得到通知,將 signal_filter 設置為通知函數,然後進入事件循環。
  
  當滿足匹配的消息被發送時,signal_func 會被調用。不過,它也將會收到來自匯流排本身的匯流排管理信號。要確定接收到消息時應該做些什麼,僅僅需要檢驗消息頭。如果消息是匯流排斷開信號,則事件循環終止,因為偵聽一個不存在的匯流排是沒有意義的。(告知匯流排信號已經處理)。然後,將到來的消息與期望的消息相比較,如果成功,則解出參數並輸出。如果到來的消息不是其中的任何一個,則告知匯流排沒有處理那個消息。
  
  那兩個示例使用了低層的 D-BUS 程序庫,這個程序庫是完全的,但是當您想創建服務和很多對象時,使用起來冗長得令人厭倦。有正在開發中的 C# 和 Python 包裝器,提供了非常接近於 D-BUS 的邏輯模型的編程介面。作為一個示例,這裡是用 Python 重新對 ping/listen 示例進行了更為精緻的實現。由於 Python 綁定模擬了邏輯介面,所以不可能不通過一個服務來發送信號。所以這個例子也要創建一個服務:
  
  清單 3. dbus-ping-send.py
  
  
QUOTE:
#! /usr/bin/env python
  
  import gtk
  import dbus
  
  # Connect to the bus
  bus = dbus.Bus()
  
  # Create a service on the bus
  service = dbus.Service("com.burtonini.dbus.SignalService", bus)
  
  # Define a D-BUS object
  class SignalObject(dbus.Object):
    def __init__(self, service):
      dbus.Object.__init__(self, "/", [], service)
  
  # Create an instance of the object, which is part of the service
  signal_object = SignalObject(service)
  
  def send_ping():
    signal_object.broadcast_signal("com.burtonini.dbus.Signal", "Ping")
    print "Ping!"
    return gtk.TRUE
  
  # Call send_ping every second to send the signal
  gtk.timeout_add(1000, send_ping)
  gtk.main()

  
  代碼大部分是不言而明的:獲得一個到匯流排的連接並註冊 com.burtonini.dbus.SignalService 服務。然後創建一個最小限度的 D-BUS 對象,這個對象每秒廣播一個信號。代碼比相應的 C 代碼更簡單,但是需要做 Python 綁定的工作。(例如,沒有方法向信號添加參數。)
  
  清單 4. dbus-ping-listen.py
  
  
QUOTE:
#! /usr/bin/env python
  
  import gtk
  import dbus
  
  bus = dbus.Bus()
  
  def signal_callback(interface, signal_name, service, path, message):
    print "Received signal %s from %s" % (signal_name, interface)
  
  # Catch signals from a specific interface and object, and call signal_callback
  # when they arrive.
  bus.add_signal_receiver(signal_callback,
              "com.burtonini.dbus.Signal", # Interface
              None, # Any service
              "/" # Path of sending object
              )
  
  # Enter the event loop, waiting for signals
  gtk.main()

  
  此代碼比 dbus-ping-listen.c 中相應的 C 代碼更簡明,也更容易讀懂。此外,有些地方需要做綁定的工作(當調用 bus.add_signal_receiver 時,用戶必須傳入一個介面和一個對象路徑;否則會創建不正常的匹配器)。這是一個微不足道的缺陷,一旦這個缺陷被修正,就可以去服務和對象路徑參數除,這將進一步提高代碼的可讀性。
  
  結束語
  D-BUS 是一個輕量級但是很強大的遠程過程調用系統,為希望使用它的應用程序帶來最小的開銷代價。 D-BUS 正由一組非常有經驗的程序員進行積極的公開開發。D-BUS 得到了早期採用者的迅速接受,因而它在 Linux 桌面領域似乎有一個樂觀的未來。




[火星人 via ] 消息匯流排和活動系統滲透到Linux桌面已經有274次圍觀

http://www.coctec.com/docs/linux/show-post-190396.html