轉:http://www.cnblogs.com/wzh206/archive/2010/05/13/1734901.htmlhtml
1. 進程間使用D-Bus通訊api
D-Bus是一種高級的進程間通訊機制,它由freedesktop.org項目提供,使用GPL許可證發行。D-Bus最主要的用途是在Linux桌面環境爲進程提供通訊,同時能將Linux桌面環境和Linux內核事件做爲消息傳遞到進程。D-Bus的主要機率爲總線,註冊後的進程可經過總線接收或傳遞消息,進程也可註冊後等待內核事件響應,例如等待網絡狀態的轉變或者計算機發出關機指令。目前,D-Bus已被大多數Linux發行版所採用,開發者可以使用D-Bus實現各類複雜的進程間通訊任務。安全
2. D-Bus的基本概念服務器
D-Bus是一個消息總線系統,其功能已涵蓋進程間通訊的全部需求,並具有一些特殊的用途。D-Bus是三層架構的進程間通訊系統,其中包括:網絡
接口層:接口層由函數庫libdbus提供,進程可經過該庫使用D-Bus的能力。數據結構
總線層:總線層其實是由D-Bus總線守護進程提供的。它在Linux系統啓動時運行,負責進程間的消息路由和傳遞,其中包括Linux內核和Linux桌面環境的消息傳遞。架構
包裝層:包裝層一系列基於特定應用程序框架的Wrapper庫。app
D-Bus具有自身的協議,協議基於二進制數據設計,與數據結構和編碼方式無關。該協議無需對數據進行序列化,保證了信息傳遞的高效性。不管是libdbus,仍是D-Bus總線守護進程,均不須要太大的系統開銷。框架
總線是D-Bus的進程間通訊機制,一個系統中一般存在多條總線,這些總線由D-Bus總線守護進程管理。最重要的總線爲系統總線(System Bus),Linux內核引導時,該總線就已被裝入內存。只有Linux內核、Linux桌面環境和權限較高的程序才能向該總線寫入消息,以此保障系統安全性,防止有惡意進程假冒Linux發送消息。less
會話總線(Session Buses)由普通進程建立,可同時存在多條。會話總線屬於某個進程私有,它用於進程間傳遞消息。
進程必須註冊後才能收到總線中的消息,而且可同時鏈接到多條總線中。D-Bus提供了匹配器(Matchers)使進程能夠有選擇性的接收消息,另外運行進程註冊回調函數,在收到指定消息時進行處理。匹配器的功能等同與路由,用於避免處理無關消息形成進程的性能降低。除此之外,D-Bus機制的重要概念有如下幾個。
對象:對象是封裝後的匹配器與回調函數,它以對等(peer-to-peer)協議使每一個消息都有一個源地址和一個目的地址。這些地址又稱爲對象路徑,或者稱之爲總線名稱。對象的接口是回調函數,它以相似C++的虛擬函數實現。當一個進程註冊到某個總線時,都要建立相應的消息對象。
消息:D-Bus的消息分爲信號(signals)、方法調用(method calls)、方法返回(method returns)和錯誤(errors)。信號是最基本的消息,註冊的進程可簡單地發送信號到總線上,其餘進程經過總線讀取消息。方法調用是經過總線傳遞參數,執行另外一個進程接口函數的機制,用於某個進程控制另外一個進程。方法返回是註冊的進程在收到相關信息後,自動作出反應的機制,由回調函數實現。錯誤是信號的一種,是註冊進程錯誤處理機制之一。
服務:服務(Services)是進程註冊的抽象。進程註冊某個地址後,便可得到對應總線的服務。D-Bus提供了服務查詢接口,進程可經過該接口查詢某個服務是否存在。或者在服務結束時自動收到來自系統的消息。
創建服務的流程: ---------------------------------- 創建一個dbus鏈接以後 -- dbus_bus_get(),爲這個dbus鏈接(DbusConnection)起名 -- dbus_bus_request_name(),這個名字將會成爲咱們在後續進行遠程調用的時候的服務名,而後咱們進入監聽循環 -- dbus_connection_read_write()。在循環中,咱們從總線上取出消息 -- dbus_connection_pop_message(),並經過比對消息中的方法接口名和方法名 -- dbus_message_is_method_call(),若是一致,那麼咱們跳轉到相應的處理中去。在相應的處理中,咱們會從消息中取出遠程調用的參數。而且創建起回傳結果的通路 -- reply_to_method_call()。回傳動做自己等同於一次不須要等待結果的遠程調用。 發送信號的流程: ---------------------------------- 創建一個dbus鏈接以後,爲這個dbus鏈接起名,創建一個發送信號的通道,注意,在創建通道的函數中,須要咱們填寫該信號的接口名和信號名 -- dbus_message_new_signal()。而後咱們把信號對應的相關參數壓進去 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic()。而後就能夠啓動發送了 -- dbus_connection_send(); dbus_connection_flush。 進行一次遠程調用的流程: ---------------------------------- 創建好dbus鏈接以後,爲這dbus鏈接命名,申請一個遠程調用通道 -- dbus_message_new_method_call(),注意,在申請遠程調用通道的時候,須要填寫服務器名,本次調用的接口名,和本次調用名(方法名)。壓入本次調用的參數 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic(),其實是申請了一個首地址,咱們就是把咱們真正要傳的參數,往這個首地址裏面送(送完以後通常都會判斷是否內存越界了)。而後就是啓動發送調用並釋放發送相關的消息結構 -- dbus_connection_send_with_reply()。這個啓動函數中帶有一個句柄。咱們立刻會阻塞等待這個句柄給咱們帶回總線上回傳的消息。當這個句柄回傳消息以後,咱們從消息結構中分離出參數。用dbus提供的函數提取參數的類型和參數 -- dbus_message_iter_init(); dbus_message_iter_next(); dbus_message_iter_get_arg_type(); dbus_message_iter_get_basic()。也就達成了咱們進行本次遠程調用的目的了。 信號接收流程: ---------------------------------- 創建一個dbus鏈接以後,爲這個dbus鏈接起名,爲咱們將要進行的消息循環添加匹配條件(就是經過信號名和信號接口名來進行匹配控制的) -- dbus_bus_add_match()。咱們進入等待循環後,只須要對信號名,信號接口名進行判斷就能夠分別處理各類信號了。在各個處理分支上。咱們能夠分離出消息中的參數。對參數類型進行判斷和其餘的處理。 dbus_connection_read_write() -------------------------------------- As long as the connection is open, this function will block until it can read or write, then read or write, then return #TRUE. If the connection is closed, the function returns #FALSE. dbus_connection_pop_message() -------------------------------------- Returns the first-received message from the incoming message queue, removing it from the queue. The caller owns a reference to the returned message. If the queue is empty, returns #NULL. dbus_connection_send() -------------------------------------- Adds a message to the outgoing message queue. Does not block to write the message to the network; that happens asynchronously. To force the message to be written, call dbus_connection_flush(). Because this only queues the message, the only reason it can fail is lack of memory. Even if the connection is disconnected, no error will be returned. @param connection the connection. @param message the message to write. @param serial return location for message serial, or #NULL if you don't care @returns #TRUE on success. dbus_connection_send_with_reply() -------------------------------------- Queues a message to send, as with dbus_connection_send(), but also returns a #DBusPendingCall used to receive a reply to the message. If no reply is received in the given timeout_milliseconds, this function expires the pending reply and generates a synthetic error reply (generated in-process, not by the remote application) indicating that a timeout occurred. A #DBusPendingCall will see a reply message before any filters or registered object path handlers. See dbus_connection_dispatch() for details on when handlers are run. A #DBusPendingCall will always see exactly one reply message, unless it's cancelled with dbus_pending_call_cancel(). If #NULL is passed for the pending_return, the #DBusPendingCall will still be generated internally, and used to track the message reply timeout. This means a timeout error will occur if no reply arrives, unlike with dbus_connection_send(). If -1 is passed for the timeout, a sane default timeout is used. -1 is typically the best value for the timeout for this reason, unless you want a very short or very long timeout. There is no way to avoid a timeout entirely, other than passing INT_MAX for the timeout to mean "very long timeout." libdbus clamps an INT_MAX timeout down to a few hours timeout though. @warning if the connection is disconnected, the #DBusPendingCall will be set to #NULL, so be careful with this. @param connection the connection @param message the message to send @param pending_return return location for a #DBusPendingCall object, or #NULL if connection is disconnected @param timeout_milliseconds timeout in milliseconds or -1 for default @returns #FALSE if no memory, #TRUE otherwise. dbus_message_is_signal() -------------------------------------- Checks whether the message is a signal with the given interface and member fields. If the message is not #DBUS_MESSAGE_TYPE_SIGNAL, or has a different interface or member field, returns #FALSE. dbus_message_iter_init() -------------------------------------- Initializes a #DBusMessageIter for reading the arguments of the message passed in. dbus_message_iter_next() -------------------------------------- Moves the iterator to the next field, if any. If there's no next field, returns #FALSE. If the iterator moves forward, returns #TRUE. dbus_message_iter_get_arg_type() -------------------------------------- Returns the argument type of the argument that the message iterator points to. If the iterator is at the end of the message, returns #DBUS_TYPE_INVALID. dbus_message_iter_get_basic() -------------------------------------- Reads a basic-typed value from the message iterator. Basic types are the non-containers such as integer and string. dbus_message_new_signal() -------------------------------------- Constructs a new message representing a signal emission. Returns #NULL if memory can't be allocated for the message. A signal is identified by its originating object path, interface, and the name of the signal. Path, interface, and signal name must all be valid (the D-Bus specification defines the syntax of these fields). @param path the path to the object emitting the signal @param interface the interface the signal is emitted from @param name name of the signal @returns a new DBusMessage, free with dbus_message_unref() dbus_message_iter_init_append() -------------------------------------- Initializes a #DBusMessageIter for appending arguments to the end of a message. @param message the message @param iter pointer to an iterator to initialize dbus_message_iter_append_basic() -------------------------------------- Appends a basic-typed value to the message. The basic types are the non-container types such as integer and string. @param iter the append iterator @param type the type of the value @param value the address of the value @returns #FALSE if not enough memory dbus_message_new_method_call() -------------------------------------- Constructs a new message to invoke a method on a remote object. Returns #NULL if memory can't be allocated for the message. The destination may be #NULL in which case no destination is set; this is appropriate when using D-Bus in a peer-to-peer context (no message bus). The interface may be #NULL, which means that if multiple methods with the given name exist it is undefined which one will be invoked. The path and method names may not be #NULL. Destination, path, interface, and method name can't contain any invalid characters (see the D-Bus specification). @param destination name that the message should be sent to or #NULL @param path object path the message should be sent to @param interface interface to invoke method on, or #NULL @param method method to invoke @returns a new DBusMessage, free with dbus_message_unref() dbus_bus_get() -------------------------------------- Connects to a bus daemon and registers the client with it. If a connection to the bus already exists, then that connection is returned. The caller of this function owns a reference to the bus. @param type bus type @param error address where an error can be returned. @returns a #DBusConnection with new ref dbus_bus_request_name() -------------------------------------- Asks the bus to assign the given name to this connection by invoking the RequestName method on the bus. First you should know that for each bus name, the bus stores a queue of connections that would like to own it. Only one owns it at a time - called the primary owner. If the primary owner releases the name or disconnects, then the next owner in the queue atomically takes over. So for example if you have an application org.freedesktop.TextEditor and multiple instances of it can be run, you can have all of them sitting in the queue. The first one to start up will receive messages sent to org.freedesktop.TextEditor, but if that one exits another will become the primary owner and receive messages. The queue means you don't need to manually watch for the current owner to disappear and then request the name again. @param connection the connection @param name the name to request @param flags flags @param error location to store the error @returns a result code, -1 if error is set 給DBusConnection起名字(命名) -- 兩個相互通訊的鏈接(connection)不能同名 命名規則: xxx.xxx (zeng.xiaolong) dbus_bus_add_match() -------------------------------------- Adds a match rule to match messages going through the message bus. The "rule" argument is the string form of a match rule. @param connection connection to the message bus @param rule textual form of match rule @param error location to store any errors dbus_pending_call_block() -------------------------------------- Block until the pending call is completed. The blocking is as with dbus_connection_send_with_reply_and_block(); it does not enter the main loop or process other messages, it simply waits for the reply in question. If the pending call is already completed, this function returns immediately. @todo when you start blocking, the timeout is reset, but it should really only use time remaining since the pending call was created. This requires storing timestamps instead of intervals in the timeout @param pending the pending call dbus_pending_call_steal_reply() -------------------------------------- Gets the reply, or returns #NULL if none has been received yet. Ownership of the reply message passes to the caller. This function can only be called once per pending call, since the reply message is tranferred to the caller. @param pending the pending call @returns the reply message or #NULL.
安裝D-Bus可在其官方網站下載源碼編譯,地址爲http://dbus.freedesktop.org。或者在終端上輸入下列指令:
安裝後,頭文件位於"/usr/include/dbus-<版本號>/dbus"目錄中,編譯使用D-Bus的程序時需加入編譯指令"`pkg-config --cflags --libs dbus-1`"。
3. D-Bus的用例
在使用GNOME桌面環境的Linux系統中,一般用GLib庫提供的函數來管理總線。在測試下列用例前,首先須要安裝GTK+開發包(見22.3節)並配置編譯環境。該用例一共包含兩個程序文件,每一個程序文件需單獨編譯成爲可執行文件。
1.消息發送程序
"dbus-ding-send.c"程序每秒經過會話總線發送一個參數爲字符串Ding!的信號。該程序的源代碼以下:
- #include <glib.h> // 包含glib庫
- #include <dbus/dbus-glib.h> // 包含 glib庫中D-Bus管理庫
- #include <stdio.h>
- static gboolean send_ding(DBusConnection *bus);// 定義發送消息函數的原型
- int main ()
- {
- GMainLoop *loop; // 定義一個事件循環對象的指針
- DBusConnection *bus; // 定義總線鏈接對象的指針
- DBusError error; // 定義D-Bus錯誤消息對象
- loop = g_main_loop_new(NULL, FALSE); // 建立新事件循環對象
- dbus_error_init (&error); // 將錯誤消息對象鏈接到D-Bus
- // 錯誤消息對象
- bus = dbus_bus_get(DBUS_BUS_SESSION, &error);// 鏈接到總線
- if (!bus) { // 判斷是否鏈接錯誤
- g_warning("鏈接到D-Bus失敗: %s", error.message);
- // 使用GLib輸出錯誤警告信息
- dbus_error_free(&error); // 清除錯誤消息
- return 1;
- }
- dbus_connection_setup_with_g_main(bus, NULL);
- // 將總線設爲接收GLib事件循環
- g_timeout_add(1000, (GSourceFunc)send_ding, bus);
- // 每隔1000ms調用一次send_ding()函數
- // 將總線指針做爲參數
- g_main_loop_run(loop); // 啓動事件循環
- return 0;
- }
- static gboolean send_ding(DBusConnection *bus) // 定義發 送消息函數的細節
- {
- DBusMessage *message; // 建立消息對象指針
- message = dbus_message_new_signal("/com/burtonini/dbus/ding",
- "com.burtonini.dbus.Signal",
- "ding"); // 建立消息對象並標識路徑
- dbus_message_append_args(message,
- DBUS_TYPE_STRING, "ding!",
- DBUS_TYPE_INVALID); //將字符串Ding!定義爲消息
- dbus_connection_send(bus, message, NULL); // 發送該消息
- dbus_message_unref(message); // 釋放消息對象
- g_print("ding!\n"); // 該函數等同與標準輸入輸出
- return TRUE;
- }
main()函數建立一個GLib事件循環,得到會話總線的一個鏈接,並將D-Bus事件處理集成到GLib事件循環之中。而後它建立了一個名爲send_ding()函數做爲間隔爲一秒的計時器,並啓動事件循環。send_ding()函數構造一個來自於對象路徑"/com/burtonini/dbus/ding"和接口"com.burtonini.dbus.Signal"的新的Ding信號。而後,字符串Ding!做爲參數添加到信號中並經過總線發送。在標準輸出中會打印一條消息以讓用戶知道發送了一個信號。
2.消息接收程序
dbus-ding-listen.c程序經過會話總線接收dbus-ding-send.c程序發送到消息。該程序的源代碼以下:
- #include <glib.h> // 包含glib庫
- #include <dbus/dbus-glib.h> // 包含glib庫中D-Bus管理庫
- static DBusHandlerResult signal_filter // 定義接收消息函數的原型
- (DBusConnection *connection, DBusMessage *message, void *user_data);
- int main()
- {
- GMainLoop *loop; // 定義一個事件循環對象的指針
- DBusConnection *bus; // 定義總線鏈接對象的指針
- DBusError error; // 定義D-Bus錯誤消息對象
- loop = g_main_loop_new(NULL, FALSE); // 建立新事件循環對象
- dbus_error_init(&error); // 將錯誤消息對象鏈接到D-Bus
- // 錯誤消息對象
- bus = dbus_bus_get(DBUS_BUS_SESSION, &error); // 鏈接到總線
- if (!bus) { // 判斷是否鏈接錯誤
- g_warning("鏈接到D-Bus失敗: %s", error.message);
- // 使用GLib輸出錯誤警告信息
- dbus_error_free(&error); // 清除錯誤消息
- return 1;
- }
- dbus_connection_setup_with_g_main(bus, NULL);
- // 將總線設爲接收GLib事件循環
- 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)
- {
- GMainLoop *loop = user_data; // 定義事件循環對象的指針,並與主函數中的同步
- if (dbus_message_is_signal // 接收鏈接成功消息,判斷是否鏈接失敗
- (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")) {
- g_main_loop_quit (loop); // 退出主循環
- return DBUS_HANDLER_RESULT_HANDLED;
- }
- if (dbus_message_is_signal(message, "com.burtonini.dbus.Signal",
- "Ping")) {
- // 指定消息對象路徑,判斷是否成功
- DBusError error; // 定義錯誤對象
- char *s;
- dbus_error_init(&error); // 將錯誤消息對象鏈接到D-Bus錯誤
- // 消息對象
- if (dbus_message_get_args // 接收消息,並判斷是否有錯誤
- (message, &error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
- g_print("接收到的消息是: %s\n", s); // 輸出接收到的消息
- dbus_free (s); // 清除該消息
- }
- else { // 有錯誤時執行下列語句
- g_print("消息已收到,但有錯誤提示: %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()函數會被調用。
若是須要肯定在接收消息時如何處理,可經過檢測消息頭實現。若收到的消息爲總線斷開信號,則主事件循環將被終止,由於監聽的總線已經不存在了。若收到其餘的消息,首先將收到的消息與期待的消息進行比較,二者相同則輸出其中參數,並退出程序。二者不相同則告知總線並無處理該消息,這樣消息會繼續保留在總線中供別的程序處理。
本文是幾篇文章的整理。