D-Bus 詳解:從編譯到應用

一、簡介

D-Bus是一種消息總線系統,是進程間通信 (IPC)的系統。

從體系結構上講,它分爲三層:

  • 一個庫libdbus,它允許兩個應用程序相互連接並交換消息。
  • 一個消息總線守護程序的可執行文件dbus-daemon,建立在libdbus之上,多個應用程序可以連接。守護程序可以將消息從一個應用程序路由到零個或多個其他應用程序。
  • 基於特定應用程序框架的綁定或包裝程序庫。例如,libdbus-glib和libdbus-qt。也有Python之類的語言的綁定。這些包裝庫是大多數人應該使用的API,因爲它們簡化了D-Bus編程的細節。libdbus旨在作爲高級綁定的低級後端。libdbus API的大部分僅對綁定實現有用。(本文不包含該部分)

libdbus僅支持一對一連接,就像原始網絡套接字一樣。但是不是通過連接發送字節流,而是發送message。消息具有標識消息類型的標頭和包含數據有效負載的正文。libdbus還抽象出所使用的數據傳輸方式(套接字與其他方式),並處理諸如身份驗證之類的細節。

消息總線守護程序構成了一個輪轂。輪轂的每個轂條都是使用libdbus與應用程序的一對一連接。應用程序通過其分支將消息發送到總線守護程序,然後總線守護程序將消息轉發到其他已連接的應用程序。可以將守護程序視爲路由器。

image

dbus 低級API實現和D-Bus協議已經進行了數年的嚴格測試,現在已“定型”。將來的更改將兼容或適當地版本化。

低級libdbus庫沒有必需的依賴項;總線守護程序唯一需要的依賴項是XML解析器(expat)。特定於特定框架(Qt,GLib,Java,C#,Python等)的更高級別的綁定添加了更多的依賴關係,使用起來更加簡單。綁定與底層的libdbus分開發展。對於諸如C#,Java和Ruby之類的語言,D-Bus協議也有一些重新實現,這些不使用libdbus參考實現。

應該注意的是,底層實現並不是主要爲應用程序作者設計的。相反,它是約束作者的基礎,也是重新實現的參考。如果可以,建議使用更高級別的綁定或實現之一。

D-Bus可以很方便地移植到任何Linux或UNIX版本,並且正在向Windows移植。

二、通信特點

D-BUS是一種低延遲、低開銷、高可用性的進程間通信機制。其協議是二進制的,避免序列化的過程,通信效率較高。D-BUS可以提供一些更高層的功能:

  • 結構化的名字空間;
  • 獨立於架構的數據格式;
  • 支持消息中的大部分通用數據元素;
  • 帶有異常處理的通用遠程調用接口;
  • 支持廣播類型的通信。

三、下載

參考實現

dbus(合併了dbus-daemon和libdbus)是D-Bus的參考實現。可以從dbus.freedesktop.org上的發行目錄:下載發行的版本,並且在所有主要的Linux發行版中都可用。

http://dbus.freedesktop.org/releases/dbus/

目前的穩定分支是DBUS 1.12.x。這是推薦版本。

目前傳統的分支DBUS 1.10.x。仍然提供支持,但僅用於安全修復程序。

較舊的分支(例如1.8.x和1.6.x)已經到期,並且不太可能發佈更多版本。

當前的開發分支是 dbus 1.13.x,它將在形成1.14.x穩定分支。開發分支沒有安全支持,該版本還包含可能破壞穩定的更改。

完全不支持諸如1.11.x和1.9.x之類的被取代的開發分支,並且以後不會有任何安全修復程序。

綁定和獨立實現

綁定和獨立的實現從Bindings Page鏈接到。

綁定包裝了libdbus(並因此自動獲得了新的身份驗證機制和其他對libdbus的添加),而重新實現則從頭開始對協議進行編碼(從而避免了對libdbus C庫的依賴,但必須與新功能保持同步) 。

源代碼

在freedesktop.org Gitlab實例上的項目dbus中可獲得D-Bus規範的最新版本和參考實現。

https://gitlab.freedesktop.org/dbus/dbus

對於其他實現和綁定,請使用源代碼存儲庫以實現適當的實現。

四、編譯

安裝cmake

在Ubuntu下直接sudo apt-get install cmake版本較低,。

提示CMake 3.0.2 or higher is required。

官網下載最新版3.17.1,然後解壓編譯即可

./configure
make
make install

安裝expat

下載 expat-2.2.9.tar.gz

官方網站:https://libexpat.github.io/

Linux編譯:

./configure --prefix=/home/share/opensource/expat

ARM編譯:

./configure --prefix=/home/share/opensource/expat_ql --host=arm-none-linux CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++ --enable-shared --enable-static

安裝:

make
make install

安裝dbus

以dbus-1.12.16版本爲例。

下載後解壓源代碼

tar -zxvf dbus-1.12.16.tar.gz

Linux編譯:

./configure --prefix=/home/share/opensource/dbus EXPAT_CFLAGS="-I/home/share/opensource/expat/include" EXPAT_LIBS="-L/home/share/opensource/expat/lib -lexpat" 

ARM編譯:

./configure --prefix=/home/share/opensource/dbus_ql --host=arm-linux CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++ EXPAT_CFLAGS="-I/home/share/opensource/expat_ql/include" EXPAT_LIBS="-L/home/share/opensource/expat_ql/lib -lexpat"

安裝:

make
make install

注意:1.8.X版本編譯和1.12.16不一致:

Linux:

./configure --prefix=/home/share/opensource/dbus_8_20 \
CFLAGS="-I/home/share/opensource/expat/include" \
LDFLAGS="-L/home/share/opensource/expat/lib -lexpat" 

ARM:

./configure --prefix=/home/share/opensource/dbus_ql_8_20 \
--host=arm-linux \
CC=arm-none-linux-gnueabi-gcc \
CXX=arm-none-linux-gnueabi-g++ --enable-shared --enable-static \
CFLAGS="-I/home/share/opensource/expat_ql/include" \
LDFLAGS="-L/home/share/opensource/expat_ql/lib -lexpat" 

生成的bin文件簡介

dbus-cleanup-sockets:用於清除目錄中的剩餘套接字。

dbus-daemon:是D-Bus消息總線守護程序。

dbus-launch:用於從Shell腳本啓動dbus-daemon。通常會從用戶的登錄腳本中調用它。

dbus-monitor:用於監視通過D-Bus消息總線的消息。

dbus-run-session:作爲新的D-Bus會話開始一個進程。

dbus-send:用於將消息發送到D-Bus消息總線。

dbus-test-tool:是D-Bus流量生成器和測試工具;它是用於調試和分析D-Bus的多功能工具。

dbus-update-activation-environment:用於更新用於D-Bus會話服務的環境; 在不使用systemd的情況下激活會話服務時,它將更新dbus-daemon --session使用的環境變量列表。

dbus-uuidgen:用於生成通用的唯一ID。

五、API手冊

API參考手冊,用於參考實現 libdbus:http://dbus.freedesktop.org/doc/api/html/index.html

六、技術實現

D-Bus 本身是構建在 Socket 機制之上。真正的通信還是由 Socket 來完成的。D-Bus 則是在這之上,制定了一些通信的協議,並提供了更高一層的接口,以更方便應用程序之間進行數據的交互。

在D-Bus的體系中,有一個常駐的進程 Daemon,所有進程間的交互都通過它來進行分發和管理。所有希望使用 D-Bus 進行通信的進程,都必須事先連上 Daemon,並將自己的名字註冊到 Daemon 上,之後,Daemon會根據需要把消息以及數據發到相應的進程中。

DBUS中主要概念爲總線,連接到總線的進程可通過總線接收或傳遞消息,總線收到消息時,根據不同的消息類型進行不同的處理。DBUS中消息分爲四類:

  1. Method call消息:將觸發一個函數調用 ;
  2. Method return消息:觸發函數調用返回的結果;
  3. Error消息:觸發的函數調用返回一個異常 ;
  4. Signal消息:通知,可以看作爲事件消息。

在一臺機器上總線守護有多個實例(instance)。這些總線之間都是相互獨立的。一個持久的系統總線(system bus)和 很多會話總線(session buses)。

  • System Bus:它在引導時就會啓動。這個總線由操作系統和後臺進程使用,安全性非常好,以使得任意的應用程序不能欺騙系統事件。 它是桌面會話和操作系統的通信,這裏操作系統一般而言包括內核和系統守護進程。 這種通道的最常用的方面就是發送系統消息,比如:插入一個新的存儲設備;有新的網絡連接;等等。
  • Session Buses:這些總線當用戶登錄後啓動,屬於那個用戶私有。它是用戶的應用程序用來通信的一個會話總線。 同一個桌面會話中兩個桌面應用程序的通信,可使得桌面會話作爲整體集成在一起以解決進程生命週期的相關問題。

應用主要包括2方面:函數調用和消息廣播

  • 函數調用:D-BUS可以實現進程間函數調用,進程A發送函數調用的請求(Method call消息),經過總線轉發至進程B。進程B將應答函數返回值(Method return消息)或者錯誤消息(Error消息)。
  • 消息廣播:進程間消息廣播(Signal消息)不需要響應,接收方需要向總線註冊感興趣的消息類型,當總線接收到“Signal消息”類型的消息時,會將消息轉發至希望接收的進程。

1. 基本概念

A… is identified by a(n)… which looks like… and is chosen by…
Bus address unix:path=/var/run/dbus/system_bus_socket system configuration
Connection bus name :34-907 (unique) or com.mycompany.TextEditor (well-known) D-Bus (unique) or the owning program (well-known)
Object path /com/mycompany/TextFileManager the owning program
Interface interface name org.freedesktop.Hal.Manager the owning program
Member member name ListNames the owning program

Address

address是用來標識dbus-daemon的。當一個dbus-daemon運行以後,其他的app該怎麼連接到這個dbus-daemon,靠的就是address。

address的格式要求像這樣:unix:path=/var/run/dbus/system_bus_socket

Bus Name

是一個每個應用程序(或是通信對象)用來標識自己。

有兩種,一種是“Unique Connection Name”,是以冒號開頭的,是全局唯一但人類不友好的命名,一種是“Well-know Name”,人類友好的。

Bus Name 的命名規則是:

  1. Bus name 就像網址一樣,由“.”號分割的多個子字符串組成,每個子字符串都必須至少有一個以上的字符。
  2. 每個子字符串都只能由“[A-Z][a-z][0-9]_-”這些 ASCII 字符組成,只有 Unique Name 的子串可以以數字開頭。
  3. 每個 Bus name 至少要有一個“.”和兩個子字符串,不能以“.”開頭
  4. Bus name 不能超過 255 個字符

D-Bus Name 是用來給應用程序進行標識自己的,所以每當程序連上 D-Bus Daemon 後,就會分配到一個 Unique Name,同時應用程序還可以要求自己分配另一個 Well-know name (通過 dbus_bus_request_name 函數)。

Interface Name

D-Bus 也有 interface 這個概念,主要是用來爲更高一層的框架使用方面而設定的。在 C API 這一層,幾乎可以無視這個概念,只需要知道這個一個“字符串”,並在消息匹配是被 D-Bus 使用到,會隨着消息在不同的進程之前傳遞,從進程 A 發送一個消息或是數據到進程 B 時,其中必定會帶有一個部分就是這個字符串,至於 B 進程怎麼用(或是無視它)都可以。它的命名規則與 D-Bus Name 幾乎是一樣的,只有一點要注意,interface name 中不能帶有“-”字符。

Object Path

D-Bus 中的 object path,與 interface 一樣,也只是個概念在更高一層的框架(QT Dbus)中才比較有用,在 C API 這一層,幾乎可以無視這個概念,把它當成一個普通的字符串,根據通信的需要,用來做一種標識和區分。Object path 的命名規則是

  1. object path 可以是任意長度的
  2. 以’/‘開頭,並以以’/'分隔的若干子字符串組成
  3. 每個子串必須由“[A-Z][a-z][0-9]_”中的字符組成
  4. 不能有空子串(也就是不能連續兩個’/'符)
  5. 除了“root path”(’/’)之外,不能再有 object path 是以 ‘/’ 結尾的了。

例子:/com/example/MusicPlayer1

Member Name

Member 包含兩種類型,一種是 Signal,一種是 Method。

在大多數方面,他們幾乎是一樣的,除了兩點:

  1. Signal是在總線中進行廣播的,而Method是指定發給某個進程的。
  2. Signal 不會有返回,而 Method 一定會有返回(同步的或是異步的)。

Member name的命名規則是這樣的:

  1. 只能包含“[A-Z][a-z][0-9]_”這些字符,且不能以數字開頭。不能包含“.”。
  2. 不能超過255個字符

以 C API 的層面來看,Member name最大的作用就是在兩個進程間共享“發出的消息的類型信息”。D-Bus 只能以 Signal / Method 來進行消息通信,這兩種方式都允許在消息發出之前,在消息中 append 各種類型的數據,當通信的對方收到消息後,它就可以通過 Signal / Method 的名稱知道如何把各種數據再解析出來。

2. 通信流程概覽

img

進程1(Process1)需先連接到總線(dbus_bus_get),其次構造消息(dbus_message_new_signal),然後發送消息(dbus_connection_send)到後臺進程。後臺進程接收消息,然後根據消息類型對消息進行不同處理(bus_dispatch_matches)。

進程2(Process2)接收消息前需要連接到總線,並告知總線自己希望得到的消息類型(dbus_bus_add_match),然後等待接收消息(dbus_connection_pop_message)。進程2(Process2)收到總線轉發的消息時會根據消息類型,做不同的處理(若是信號類型則不需要發送返回值給總線)。

3. 連接到總線

相關接口

typedef enum
{
  DBUS_BUS_SESSION,    /**< The login session bus */
  DBUS_BUS_SYSTEM,     /**< The systemwide bus */
  DBUS_BUS_STARTER     /**< The bus that started us, if any */
} DBusBusType;
 
DBusConnection  *dbus_bus_get  (DBusBusType  type,  DBusError   *error) /*  建立和總線的連接  */  
  
int  dbus_bus_request_name  (DBusConnection   *connection,  
                             const char         *name,  
                             unsigned int        flags,  
                             DBusError        *error) /*  註冊連接名稱 */  
  
void  dbus_connection_close  (DBusConnection  *connection) /*  關閉連接 */  

實現示例

首先使用

connection = dbus_bus_get(DBUS_BUS_SESSION, &err);

讓應用程序和 D-Bus 之間取得連接。DBUS_BUS_SESSION 表示使用的是Session bus。

之後,使用函數:

ret = dbus_bus_request_name(connection, "hello.world.client",
                            DBUS_NAME_FLAG_REPLACE_EXISTING, &err);

將自己的進程名字註冊到 Daemon 上。DBUS_NAME_FLAG_REPLACE_EXISTING表示使用bus name程序如果已經存在的話,請求替換當前主所有者。

4. 構建數據

相關接口

DBusMessage  *dbus_message_new_signal  (const  char  *path,  
                                       const  char  *iface,  
                                       const  char  *name) /*  創建信號類型消息 */  
  
    
DBusMessage  *dbus_message_new_method_call  (const char  *destination,  
                                             const char  *path,  
                                             const char  *iface,  
                                             const char  *method) /*  創建一個函數調用消息 */  
DBusMessage  *dbus_message_new_method_return (DBusMessage *method_call) /*  創建返回消息 */ 
    
DBusMessage* dbus_message_new_error	(DBusMessage *reply_to,
                                     const char  *error_name,
                                     const char  *error_message);/* 創建Error消息 */

void  dbus_message_iter_init_append  (DBusMessage     *message,  
                          DBusMessageIter  *iter) /*  爲消息添加參數 */  
    
dbus_bool_t dbus_message_iter_append_basic(DBusMessageIter *iter,
                                           int              type,
                                           const void      *value);/*  添加具體參數 */ 

void dbus_message_unref (DBusMessage   *message);/*  釋放內存 */ 

實現示例

上文已提到,dbus中包括四種消息。

Method call消息:

msg = dbus_message_new_method_call("hello.world.service", 
                                       "/hello/world","hello.world", "add");
dbus_message_iter_init_append(msg, &arg);
dbus_message_iter_append_basic (&arg, DBUS_TYPE_INT32,&a);
dbus_message_iter_append_basic (&arg, DBUS_TYPE_INT32,&b);

Method return消息:

rp = dbus_message_new_method_return(msg);
dbus_message_iter_init_append(rp, &r_arg);
dbus_message_iter_append_basic(&r_arg, DBUS_TYPE_INT32, &sum);

Error消息:

rp = dbus_message_new_error(msg,DBUS_ERROR_FAILED,"test");

根據D-Bus規範中給出的語法,錯誤名稱必須是有效的錯誤名稱。

如果不想編一個錯誤名,請使用DBUS_ERROR_FAILED “org.freedesktop.DBus.Error.Failed”

Signal消息:

msg = dbus_message_new_signal("/hello", "aa.bb.cc", "alarm_test");
dbus_message_iter_init_append(msg, &arg);
dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &str);

DBusMessage 是 DBus 中的核心數據結構。可以這樣理解:DBus中傳遞消息數據的時候,就是通過它來傳遞的。對於使用者來說,DBusMessage 中存儲了兩種重要的信息,一種是爲通信機制服務的各種 Name,一種是通信的數據本身。

DBus 提供了一個 DBusMessageIter 的類型,使用這個類型的變量,我們就可以向 DBusMessage 中很容易地加入數據,也可以很容易地從中取出數據。

dbus_message_iter_append_basic函數向 DBusMessageIter 中追加一些“基本類型”(basic)的數據,所謂基本類型的數據,在 DBus 中是這麼定義的:

Conventional Name Encoding Alignment
INVALID Not applicable; cannot be marshaled. N/A
BYTE A single 8-bit byte. 1
BOOLEAN As for UINT32, but only 0 and 1 are valid values. 4
INT16 16-bit signed integer in the message’s byte order. 2
UINT16 16-bit unsigned integer in the message’s byte order. 2
INT32 32-bit signed integer in the message’s byte order. 4
UINT32 32-bit unsigned integer in the message’s byte order. 4
INT64 64-bit signed integer in the message’s byte order. 8
UINT64 64-bit unsigned integer in the message’s byte order. 8
DOUBLE 64-bit IEEE 754 double in the message’s byte order. 8
STRING A UINT32 indicating the string’s length in bytes excluding its terminating nul, followed by non-nul string data of the given length, followed by a terminating nul byte. 4 (for the length)
OBJECT_PATH Exactly the same as STRING except the content must be a valid object path (see above). 4 (for the length)
SIGNATURE The same as STRING except the length is a single byte (thus signatures have a maximum length of 255) and the content must be a valid signature (see above). 1
ARRAY A UINT32 giving the length of the array data in bytes, followed by alignment padding to the alignment boundary of the array element type, followed by each array element. 4 (for the length)
STRUCT A struct must start on an 8-byte boundary regardless of the type of the struct fields. The struct value consists of each field marshaled in sequence starting from that 8-byte alignment boundary. 8
VARIANT The marshaled SIGNATURE of a single complete type, followed by a marshaled value with the type given in the signature. 1 (alignment of the signature)
DICT_ENTRY Identical to STRUCT. 8
UNIX_FD 32-bit unsigned integer in the message’s byte order. The actual file descriptors need to be transferred out-of-band via some platform specific mechanism. On the wire, values of this type store the index to the file descriptor in the array of file descriptors that accompany the message. 4

結構體消息構建

關於如何便捷傳輸結構體消息,請參考如下API。

接口:

dbus_bool_t dbus_message_iter_open_container     (DBusMessageIter *iter,
                                                  int              type,
                                                  const char      *contained_signature,
                                                  DBusMessageIter *sub);
dbus_bool_t dbus_message_iter_append_fixed_array (DBusMessageIter *iter,
                                                  int              element_type,
                                                  const void      *value,
                                                  int              n_elements);           dbus_bool_t dbus_message_iter_close_container    (DBusMessageIter *iter,
                                                  DBusMessageIter *sub);                                       

示例:

//將容器類型的值附加到消息。	
dbus_message_iter_open_container(&arg,DBUS_TYPE_ARRAY,DBUS_TYPE_BYTE_AS_STRING,&arg_sub);
//將固定長度值塊追加到數組。
dbus_message_iter_append_fixed_array(&arg_sub, DBUS_TYPE_BYTE, &req,sizeof(ydsRteRcReq_t));
//關閉附加到消息的容器類型值
dbus_message_iter_close_container(&arg, &arg_sub); 

注意:傳輸數據需要是指針的地址

5. 發送數據

相關接口

dbus_bool_t  dbus_connection_send  ( DBusConnection  *connection,  
                                     DBusMessage    *message,  
                                     dbus_uint32_t    *serial) /*  發送消息到總線 */  
void  dbus_message_unref  (DBusMessage *message) /*  釋放消息 */ 
    
dbus_bool_t  dbus_connection_send_with_reply  (DBusConnection   *connection,  
                                               DBusMessage      *message,   
                                               DBusPendingCall  **pending_return,  
                                               int timeout_milliseconds) /*  發送消息,等待回覆 */  
void dbus_connection_flush (DBusConnection *connection); /* 發送消息 */ 

dbus_connection_flush 這個函數的作用是“Blocks until the outgoing message queue is empty.”,可以簡單地理解爲調用這個函數可以使用得發送進程一直等消息發送完了才能繼續運行。

實現示例

發送Method return/Signa/Error數據

//入隊
dbus_connection_send(connection, msg, NULL);
//發送
dbus_connection_flush(connection);
//釋放內存
dbus_message_unref(msg);

發送Method call數據,等待回覆:

//入隊message,等待回覆
//param1: 連接描述符
//param2: message
//param3: 相當於一個回調的一個描述符,爲了獲了返回的消息
//param4: 超時間. -1代表無限
dbus_connection_send_with_reply (connection, msg, &pending, -1);
dbus_connection_flush(connection);
dbus_message_unref(msg);

6. 接收數據

相關接口

void  dbus_bus_add_match  ( DBusConnection  *connection,  
                            const char        *rule,  
                            DBusError       *error) /*  告知總線感興趣的消息 */  
  
DBusMessage  *dbus_connection_pop_message  ( DBusConnection  *connection) /*  接收消息  */  
void  dbus_pending_call_block  (DBusPendingCall  *pending) /*  阻塞等待返回值 */  
  
DBusMessage  *dbus_pending_call_steal_reply  (DBusPendingCall  *pending) /* 獲得返回消息*/ 
   
DBusMessage  *dbus_connection_pop_message  ( DBusConnection  *connection) /*  從總線獲取消息 */   

實現示例

	//註冊感興趣的signal
	//param1: 連接描述符
	//param2: match rule (常用的類型: sender=
	//									interface=
	//									type=
	//									member= )
	//param3: err info
	//只設置一個type = signal,表示所有信號都接受.也可以加上接口,發送者bus_name
	dbus_bus_add_match(connection, "type='signal'", &err);

Signal消息使用dbus_bus_add_match函數向 Daemon 添加匹配信號,讓 Daemon 知道自己對這種信號感興趣。

可以使用下面的函數來進行等待:

//param1: 連接描述符
//param2: 超時時間, -1無限超時時間
dbus_connection_read_write(conn, 0);
//從隊列中取出一條消息
msg = dbus_connection_pop_message(conn);

一旦有消息發送過來, 就可以通過 msg 取到相應的數據了。

對Method return消息:

//阻塞,直到接收到一個響應.
dbus_pending_call_block (pending);
//從pending中取出msg
msg = dbus_pending_call_steal_reply (pending);

之後可以進行數據解析。

7. 解析數據

相關接口

dbus_bool_t  dbus_message_is_method_call (DBusMessage  *message,  
                                          const char     *iface,  
                                          const char     *method) /*  判定消息是方法調用 */ 

dbus_bool_t  dbus_message_is_signal  (DBusMessage  *message,  
                                      const char      *iface,  
                                      const char     *signal_name) /*  判斷消息是否爲信號 */ 
    
dbus_bool_t  dbus_message_iter_init  (DBusMessage     *message,  
                          DBusMessageIter  *iter) /*  獲取參數 */ 
    
int         dbus_message_iter_get_arg_type (DBusMessageIter *iter);/*  返回參數類型。 */ 

void        dbus_message_iter_get_basic (DBusMessageIter *iter,
                                         void 	*value);/*  讀取基本類型值 */ 

dbus_bool_t dbus_message_iter_next (DBusMessageIter *iter);/*  下一個參數 */ 

實現示例

消息判斷:

dbus_message_is_signal(msg, "aa.bb.cc", "alarm_test");
dbus_message_is_method_call(msg, "hello.world", "add");

使用dbus_message_is_signal/dbus_message_is_method_call判斷消息類型。

消息解析:

	if(!dbus_message_iter_init(msg, &arg))
	{
		printf("no argument!\n");
		goto out;
	}
	if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INT32)//1
	{
		printf("argument error\n");
		goto out;
	}
	dbus_message_iter_get_basic(&arg, &a);//2
 
	if(!dbus_message_iter_next(&arg))
	{
		printf("too few argument!\n");
		goto out;
	}
	dbus_message_iter_get_basic(&arg, &b);

先使用 dbus_messge_iter_init 先把 DBusMessage 對象和從 DBus 總線中取到的 msg 關聯起來。

這樣,使用1處的函數先取得第一個通信數據中第一個參數的類型,如果類型無誤的話可以進而使用2處的函數取得參數值本身。

使用dbus_message_iter_next迭代下一個參數,重複上述步驟。

結構體消息解析

關於如何便捷解析結構體消息,請參考如下API。

接口:

void        dbus_message_iter_recurse          (DBusMessageIter *iter,
                                                DBusMessageIter *sub);
void        dbus_message_iter_get_fixed_array  (DBusMessageIter *iter,
                                                void            *value,
                                                int             *n_elements);

示例:

//從消息讀取值時遞歸到容器值,初始化子迭代器以用於遍歷容器的子值。
dbus_message_iter_recurse(&arg, &arg_sub);
//從消息迭代器中讀取固定長度值塊。
dbus_message_iter_get_fixed_array(&arg_sub,&req,&num);

注意:傳輸數據需要是指針的地址

七、權限配置文件

D-Bus配置文件 D-Bus消息守護進程的配置文件配置了總線的類型,資源限制,安全參數等。 配置文件的格式並不是標準的一部分,也不保證向後兼容。

標準的系統總線和會話總線在“/etc/dbus-1/system.conf”和“/etc/dbus-1/session.conf”(1.12.16在“/share/dbus-1/system.conf”和“/share/dbus-1/session.conf”)。

上面列出的配置文件可能不應修改。如果需要更改,則應創建/etc/dbus-1/session-local.conf和/或 /etc/dbus-1/system-local.conf對這些文件進行任何所需的更改。

該配置文件的格式就是一個xml文檔,且必須有如下類型聲明:

<!DOCTYPE busconfig PUBLIC “-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN”
“http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd“>

根元素

一般爲system或session。

在當前位置包含該文件,如果是相對目錄,則應爲相對於當前配置文件所在目錄,有一個選項”ignore_missing=(yes|no)”,默認爲yes

在當前位置包含該目錄所有的配置文件,目錄中文件的包含順序不定,且只有以”.conf”結尾的文件纔會被包含。

daemon運行的用戶,可以爲用戶名或uid,如果守護進程無法切換到該用戶就會自動退出,如果有多個配置項,會採用最後一個。

進程成爲一個真正的守護進程。

<keep_umask> 若存在,守護進程在fork時會保留原來的umask。

總線監聽的地址,地址爲包含傳輸地址加參數/選項的標準D-Bus格式,例如:

<listen>unix:path=/tmp/foo</listen>
<listen>tcp:host=localhost,port=1234</listen>

指定授權機制。如果不存在,所有已知的機制都被允許。如果有多項配置,則所有列出的機制都被允許。

添加掃描.service文件的目錄,Service用於告訴總線如何自動啓動一個程序,主要用於每用戶的session bus。

<standard_session_servicedirs/> 等效於設定一系列的元素, “XDG Base Directory Specification”

<standard_system_servicedirs/> 設定標準的系統級service搜索目錄,默認爲/usr/share/dbus-1/system-services, 只用於/etc/dbus-1/system.conf.定義的系統級總線,放在其他配置文件中無效。

設定setuid helper,使用設置的用戶啓動系統服務的守護進程,一般來說應該是dbus-daemon-launch-helper。 該選項僅用於系統總線。

資源限制一般用於系統總線。 設置資源限制,例如:

<limit name="max_message_size">64</limit>
<limit name="max_completed_connections">512</limit>

可用的限制名有:

"max_incoming_bytes"         : total size in bytes of messages
                               incoming from a single connection
"max_incoming_unix_fds"      : total number of unix fds of messages
                               incoming from a single connection
"max_outgoing_bytes"         : total size in bytes of messages
                               queued up for a single connection
"max_outgoing_unix_fds"      : total number of unix fds of messages
                               queued up for a single connection
"max_message_size"           : max size of a single message in
                               bytes
"max_message_unix_fds"       : max unix fds of a single message
"service_start_timeout"      : milliseconds (thousandths) until
                               a started service has to connect
"auth_timeout"               : milliseconds (thousandths) a
                               connection is given to
                               authenticate
"max_completed_connections"  : max number of authenticated connections
"max_incomplete_connections" : max number of unauthenticated
                               connections
"max_connections_per_user"   : max number of completed connections from
                               the same user
"max_pending_service_starts" : max number of service launches in
                               progress at the same time
"max_names_per_connection"   : max number of names a single
                               connection can own
"max_match_rules_per_connection": max number of match rules for a single
                                  connection
"max_replies_per_connection" : max number of pending method
                               replies per connection
                               (number of calls-in-progress)
"reply_timeout"              : milliseconds (thousandths)
                               until a method call times out

定義用於一組特定連接的安全策略,策略由和元素組成。 策略一般用於系統總線,模擬防火牆的功能來只允許期望的連接。 當前系統總線的默認策略會阻止發送方法調用和獲取總線名字,其他的如消息回覆、信號等是默認允許的。

通常來說,最好是保證系統服務儘可能的小,目標程序在自己的進程中運行並且提供一個總線名字來提供服務。 規則使得程序可以設置總線名字,<send_destination>允許一些或所有的uid訪問我們的服務。

元素可以有下面四個屬性中的一個: context=”(default|mandatory)” at_console=”(true|false)” user=”username or userid” group=”group name or gid”

策略以下面的規則應用到連接:

  • 所有 context=”default” 的策略被應用
  • 所有 group=”connection’s user’s group” 的策略以不定的順序被應用
  • 所有 user=”connection’s auth user” 的策略以不定順序被應用
  • 所有 at_console=”true” 的策略被應用
  • 所有 at_console=”false” 的策略被應用
  • 所有 context=”mandatory” 的策略被應用 後應用的策略會覆蓋前面的策略。

和出現在元素下面,禁止一些動作,而則創建上面元素的一些例外。 這兩個元素可用的屬性包括:

send_interface=”interface_name”
send_member=”method_or_signal_name” send_error=”error_name” send_destination=”name” send_type=”method_call” | “method_return” | “signal” | “error” send_path=”/path/name”

receive_interface=”interface_name” receive_member=”method_or_signal_name” receive_error=”error_name” receive_sender=”name” receive_type=”method_call” | “method_return” | “signal” | “error” receive_path=”/path/name”

send_requested_reply=”true” | “false” receive_requested_reply=”true” | “false”

eavesdrop=”true” | “false”

own=”name” own_prefix=”name” user=”username” group=”groupname”

send_destination跟receive_sender是指發送到目的爲或者收到來自於該名字的owner,而不是該名字。因此 如果一個連接有三個服務A、B、C,如果拒絕發送到A,那麼發送到B和C也不行。 其他的send_*receive_*則匹配消息頭的字段。

在session.conf中:

<!-- This configuration file controls the per-user-login-session message bus.
     Add a session-local.conf and edit that rather than changing this 
     file directly. -->

<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <!-- Our well-known bus type, don't change this -->
  <type>session</type>

  <!-- If we fork, keep the user's original umask to avoid affecting
       the behavior of child processes. -->
  <keep_umask/>

  <listen>unix:tmpdir=/tmp</listen>

  <!-- On Unix systems, the most secure authentication mechanism is
  EXTERNAL, which uses credential-passing over Unix sockets.

  This authentication mechanism is not available on Windows,
  is not suitable for use with the tcp: or nonce-tcp: transports,
  and will not work on obscure flavours of Unix that do not have
  a supported credentials-passing mechanism. On those platforms/transports,
  comment out the <auth> element to allow fallback to DBUS_COOKIE_SHA1. -->
  <auth>EXTERNAL</auth>

  <standard_session_servicedirs />

  <policy context="default">
    <!-- Allow everything to be sent -->
    <allow send_destination="*" eavesdrop="true"/>
    <!-- Allow everything to be received -->
    <allow eavesdrop="true"/>
    <!-- Allow anyone to own anything -->
    <allow own="*"/>
  </policy>

  <!-- Config files are placed here that among other things, 
       further restrict the above policy for specific services. -->
  <includedir>session.d</includedir>

  <!-- This is included last so local configuration can override what's 
       in this standard file -->
  <include ignore_missing="yes">session-local.conf</include>

  <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>

  <!-- For the session bus, override the default relatively-low limits 
       with essentially infinite limits, since the bus is just running 
       as the user anyway, using up bus resources is not something we need 
       to worry about. In some cases, we do set the limits lower than 
       "all available memory" if exceeding the limit is almost certainly a bug, 
       having the bus enforce a limit is nicer than a huge memory leak. But the 
       intent is that these limits should never be hit. -->

  <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max -->
  <limit name="max_incoming_bytes">1000000000</limit>
  <limit name="max_incoming_unix_fds">250000000</limit>
  <limit name="max_outgoing_bytes">1000000000</limit>
  <limit name="max_outgoing_unix_fds">250000000</limit>
  <limit name="max_message_size">1000000000</limit>
  <!-- We do not override max_message_unix_fds here since the in-kernel
       limit is also relatively low -->
  <limit name="service_start_timeout">120000</limit>  
  <limit name="auth_timeout">240000</limit>
  <limit name="pending_fd_timeout">150000</limit>
  <limit name="max_completed_connections">100000</limit>  
  <limit name="max_incomplete_connections">10000</limit>
  <limit name="max_connections_per_user">100000</limit>
  <limit name="max_pending_service_starts">10000</limit>
  <limit name="max_names_per_connection">50000</limit>
  <limit name="max_match_rules_per_connection">50000</limit>
  <limit name="max_replies_per_connection">50000</limit>

</busconfig>

可以看到默認是允許所有消息進行發送的。

在system.conf中:

<!-- This configuration file controls the systemwide message bus.
     Add a system-local.conf and edit that rather than changing this 
     file directly. -->

<!-- Note that there are any number of ways you can hose yourself
     security-wise by screwing up this file; in particular, you
     probably don't want to listen on any more addresses, add any more
     auth mechanisms, run as a different user, etc. -->

<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>

  <!-- Our well-known bus type, do not change this -->
  <type>system</type>

  <!-- Run as special user -->
  <user>messagebus</user>

  <!-- Fork into daemon mode -->
  <fork/>

  <!-- We use system service launching using a helper -->
  <standard_system_servicedirs/>

  <!-- This is a setuid helper that is used to launch system services -->
  <servicehelper>/home/share/opensource/dbus_8_20/libexec/dbus-daemon-launch-helper</servicehelper>

  <!-- Write a pid file -->
  <pidfile>/home/share/opensource/dbus_8_20/var/run/dbus/pid</pidfile>

  <!-- Enable logging to syslog -->
  <syslog/>

  <!-- Only allow socket-credentials-based authentication -->
  <auth>EXTERNAL</auth>

  <!-- Only listen on a local socket. (abstract=/path/to/socket 
       means use abstract namespace, don't really create filesystem 
       file; only Linux supports this. Use path=/whatever on other 
       systems.) -->
  <listen>unix:path=/home/share/opensource/dbus_8_20/var/run/dbus/system_bus_socket</listen>

  <policy context="default">
    <!-- All users can connect to system bus -->
    <allow user="*"/>

    <!-- Holes must be punched in service configuration files for
         name ownership and sending method calls -->
    <deny own="*"/>
    <deny send_type="method_call"/>

    <!-- Signals and reply messages (method returns, errors) are allowed
         by default -->
    <allow send_type="signal"/>
    <allow send_requested_reply="true" send_type="method_return"/>
    <allow send_requested_reply="true" send_type="error"/>

    <!-- All messages may be received by default -->
    <allow receive_type="method_call"/>
    <allow receive_type="method_return"/>
    <allow receive_type="error"/>
    <allow receive_type="signal"/>

    <!-- Allow anyone to talk to the message bus -->
    <allow send_destination="org.freedesktop.DBus"/>
    <!-- But disallow some specific bus services -->
    <deny send_destination="org.freedesktop.DBus"
          send_interface="org.freedesktop.DBus"
          send_member="UpdateActivationEnvironment"/>
    <deny send_destination="org.freedesktop.DBus"
          send_interface="org.freedesktop.systemd1.Activator"/>
  </policy>

  <!-- Only systemd, which runs as root, may report activation failures. -->
  <policy user="root">
    <allow send_destination="org.freedesktop.DBus"
           send_interface="org.freedesktop.systemd1.Activator"/>
  </policy>

  <!-- Config files are placed here that among other things, punch 
       holes in the above policy for specific services. -->
  <includedir>system.d</includedir>

  <!-- This is included last so local configuration can override what's 
       in this standard file -->
  <include ignore_missing="yes">system-local.conf</include>

  <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>

</busconfig>

八、D-Bus會話守護程序

dbus-deamon是一個D-Bus消息總線daemon,運行在後臺,它支持兩個應用進程間一對一的通信,dbus-deamon也是用libdbus實現的。

系統啓動之後,有兩個dbus daemon的實例,一個稱爲system,一個稱爲session(如果是多個用戶,那麼會每個用戶啓動一個),這個實例配置不同,權限也不同。

system 實例使用的配置文件=/etc/dbus-1/system.conf

session實例使用的配置文件=/etc/dbus-1/session.conf

一般來說system daemon,被init script啓動,具有root權根,大部分功能用於廣播系統事件,比如插撥設備。session daemon用於不同桌面的進程通信或不同進程間的通信。

命令參數

dbus-daemon [--version] [--session] [--system] [--config-file=FILE] [--print-address[=DESCRIPTOR]] [--print-pid[=DESCRIPTOR]] [--introspect] [--address=ADDRESS] [--nopidfile] [--nofork] [--fork] [--systemd-activation]

這些設置選項優先級大於配置文件所配置的

–version:版本

–session:針對每個登錄用戶,普通用戶(普通權限的dbus daemon)

–system:針對系統用戶,超級權限(有特權的dbus daemon)

–config-file=FILE:指定dbus daemon相關配置文件位置

–fork:讓dbus daemon fork一個進程出來

–nofork:不需要fork

–print-address[=DESCRIPTOR]:打印出dbus daemon監聽地址.

–print-pid[=DESCRIPTOR]:打印出dbus daemon pid

–introspect:打印出dbus daemon內部實現的方法

–address:設置監聽地址

–nopidfile:不寫pid 到文件(配置文件會配置一個文件來記錄dbus daemon pid)

–systemd-activation:可能跟systemd啓動服務有關

–syslog:強制dbus daemon,用syslog(即會記錄log,也會輸出到標準輸出)

–syslog-only:僅做 syslog

–nosyslog:僅做標準輸出

例子

dbus-daemon --session --print-address --nofork --print-pid

開啓dbus-daemon session類型,打印出監聽地址,打印出pid, 且不做fork

dbus-daemon --session --print-address --fork --print-pid

fork一個進程

dbus-daemon --session --print-address --fork --print-pid --introspect

打印出dbus daemon內部所有的方法

dbus-daemon --session --print-address --fork --print-pid --address=unix:abstract=/tmp/dbus-123456

指定一個監聽地址

開機啓動

要在系統重新引導時自動啓動dbus-daemon,請/etc/rc.d/init.d/dbusblfs-bootscripts-20200404軟件包中安裝引導 腳本。(未實踐測試,僅記錄提供參考,ARM、Linux中已存在dbus-daemon system)

make install-dbus

請注意,此啓動腳本僅啓動系統範圍的 D-Bus守護程序。每個需要訪問D-Bus 服務的用戶也將需要運行會話守護程序。使用dbus-launch 命令可以使用許多方法來啓動會話守護程序 ,如上。

使用dbus-launch指定要運行的程序。(在還使用*–exit-with-session* 參數時)這樣做的好處是,當指定的程序停止時,停止會話守護程序。

還可以通過添加以下行來在系統或個人啓動腳本中啓動會話守護程序:

# Start the D-Bus session daemon
eval `dbus-launch`
export DBUS_SESSION_BUS_ADDRESS

退出shell時,此方法不會停止會話守護程序,因此應在~/.bash_logout文件中添加以下行:

# Kill the D-Bus session daemon
kill $DBUS_SESSION_BUS_PID

九、應用

完整示例一

與上面實現片段對應的完整代碼:

發送方進程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dbus/dbus.h>
 
DBusConnection* init_bus()
{
	DBusConnection *connection;
	DBusError err;
	int ret;
 
	dbus_error_init(&err);
 
	connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
	if(dbus_error_is_set(&err))
	{
		printf("connection error: :%s -- %s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}
 
	ret = dbus_bus_request_name(connection, "hello.world.client", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
	if(dbus_error_is_set(&err))
	{
		printf("Name error: %s -- %s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}
	if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
		return NULL;
 
	return connection;
}
 
 
void send_signal(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	char *str = "hello world!";
 
 
	//創建一個signal對象
	//param1: path (這個邏輯來說,可以是任何字符串,只要符合規則即可)
	//param2: interface (一樣)
	//param3: 信號方法名(必須與服務端名匹配)
	if((msg = dbus_message_new_signal("/hello", "aa.bb.cc", "alarm_test")) == NULL)
	{
		printf("message is NULL\n");
		return;
	}
#if 0
	 //這個看需求添加,一般來說,信號是一種單向廣播,加上這一句變單向單播
	 //param2: bus_name
	if(!dbus_message_set_destination(msg, "hello.world.service"))
        {
                printf("memory error\n");
        }
#endif
 
	//添加參數的一些接口
	dbus_message_iter_init_append(msg, &arg);
	dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &str);
	//入隊
	dbus_connection_send(connection, msg, NULL);
	//發送
	dbus_connection_flush(connection);
	//釋放內存
	dbus_message_unref(msg);
 
	return;
}
 
void send_method_call(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	DBusPendingCall *pending;
	int a = 100;
	int b = 99;
	int sum;
 
	msg = dbus_message_new_method_call("hello.world.service", "/hello/world","hello.world", "add");
	if(msg == NULL)
	{
		printf("no memory\n");
		return;
	}
 
	dbus_message_iter_init_append(msg, &arg);
    	if(!dbus_message_iter_append_basic (&arg, DBUS_TYPE_INT32,&a)){
        	printf("no memory!");
        	dbus_message_unref(msg);
        	return;
    	}
   	if(!dbus_message_iter_append_basic (&arg, DBUS_TYPE_INT32,&b)){
        	printf("no memory!");
        	dbus_message_unref(msg);
        	return;
    	}
 
    //入隊message,等待回覆
    //param1: 連接描述符
    //param2: message
    //param3: 相當於一個回調的一個描述符,爲了獲了返回的消息
    //param4: 超時間. -1代表無限
    if(!dbus_connection_send_with_reply (connection, msg, &pending, -1)){
        printf("no memeory!");
        dbus_message_unref(msg);
        return;
    }
 
    if(pending == NULL){
        printf("Pending is NULL, may be disconnect...\n");
        dbus_message_unref(msg);
        return;
    }
    //send
    dbus_connection_flush(connection);
    dbus_message_unref(msg);
	
	//阻塞,直到接收到一個響應.
    dbus_pending_call_block (pending);
    msg = dbus_pending_call_steal_reply (pending);
    if (msg == NULL) {
    	printf("reply is null. error\n");
    	return;
    }
    //釋放pending內存 
    dbus_pending_call_unref(pending);
    //解析參數
    if (!dbus_message_iter_init(msg, &arg))
        printf("no argument, error\n");
    if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INT32)
    {
    	printf("paramter type error\n");
    }
 
    dbus_message_iter_get_basic(&arg, &sum);
 
    printf(" a(%d) + b(%d) = %d\n",a, b, sum);
    dbus_message_unref(msg);
	
    return;
}
 
 
int main(int argc, char **argv)
{
	DBusConnection *connection;
 
	connection = init_bus();
	if(connection == NULL)
	{
		printf("connect to bus failed...\n");
		return -1;
	}
 while(1)
 {
	send_signal(connection);
	send_method_call(connection);
	sleep(5);
 }
	
	
	return 0;
}

接收方進程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus.h>
#include <unistd.h>

DBusConnection* init_bus()
{
	DBusConnection *connection;
	DBusError err;
	int ret = 0;
 
	dbus_error_init(&err);
 
	//與session dbus 建立連接
	//param1:bus type = {DBUS_BUS_SESSION, DBUS_BUS_SYSTEM} 一個系統dbus, 一個普通用戶dbus
	//param2:錯誤信息,包括錯誤名與錯誤信息.
	connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
	if(dbus_error_is_set(&err))
	{
		printf("Connection Error: %s--%s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}
 
	//爲連接設置一個bus name: bus_name;
	//param 1: 連接描述符
	//param 2: 請求bus要分配的bus name(邏輯上講,bus name可以是任何字符串,只要符合命名規則)
	//param 3: flags ={DBUS_NAME_FLAG_REPLACE_EXISTING, 
	//					DBUS_NAME_FLAG_ALLOW_REPLACEMENT,
	//					DBUS_NAME_FLAG_DO_NOT_QUEUE
	//					 }
	//param 4: err info
	ret = dbus_bus_request_name(connection, "hello.world.service", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
	if(dbus_error_is_set(&err))
	{ printf("Name Error: %s--%s\n", err.name, err.message);
		dbus_error_free(&err);
		return NULL;
	}
 
	if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
		return NULL;
 
	//註冊感興趣的signal: 來自接口dbus.test.signal.sender
	//param1: 連接描述符
	//param2: match rule (常用的類型: sender=
	//									interface=
	//									type=
	//									member= )
	//param3: err info
	//只設置一個type = signal,表示所有信號都接受.也可以加上接口,發送者bus_name
	dbus_bus_add_match(connection, "type='signal'", &err);
	//阻塞,直到消息發送成功.
	dbus_connection_flush(connection);
	if(dbus_error_is_set(&err))
	{
		printf("add Match Error %s--%s\n", err.name, err.message);
		dbus_error_free(&err);
		return connection;
	}
	return connection;
}
 
 void send_signal(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	char *str = "test1 send signal!";
 
 
	//創建一個signal對象
	//param1: path (這個邏輯來說,可以是任何字符串,只要符合規則即可)
	//param2: interface (一樣)
	//param3: 信號方法名(必須與服務端名匹配)
	if((msg = dbus_message_new_signal("/test1", "AA.BB.CC", "signal_test1")) == NULL)
	{
		printf("message is NULL\n");
		return;
	}
#if 0
	 //這個看需求添加,一般來說,信號是一種單向廣播,加上這一句變單向單播
	 //param2: bus_name
	if(!dbus_message_set_destination(msg, "hello.world.service"))
        {
                printf("memory error\n");
        }
#endif
 
	//添加參數的一些接口
	dbus_message_iter_init_append(msg, &arg);
	dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &str);
	//入隊
	dbus_connection_send(connection, msg, NULL);
	//發送
	dbus_connection_flush(connection);
	//釋放內存
	dbus_message_unref(msg);
 
	return;
}
 
void handle_message(DBusConnection *connection)
{
	DBusMessage *msg;
	DBusMessageIter arg;
	char *str;
 
	while(1)
	{
		//param1: 連接描述符
		//param2: 超時時間, -1無限超時時間
		dbus_connection_read_write(connection, 0);
		//從隊列中取出一條消息
		msg = dbus_connection_pop_message(connection); 
		if(msg == NULL)
		{
			sleep(1);
			continue;
		}
		//這裏應該過濾path,暫且不做
		//打印出消息對象路徑
		//printf("path: %s\n", dbus_message_get_path (msg));
		//param1: message
		//param2: interface 這個名字必須與發送那個接口一樣.才能處理
		//param3: singal name 方法名也必須一樣.
		if(dbus_message_is_signal(msg, "aa.bb.cc", "alarm_test"))
		{
			//解析message 參數,0爲無參數.
			if(!dbus_message_iter_init(msg, &arg))
			{
				printf("no argument\n");
			}
			//獲取第一個參數類型
			if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INVALID)
			{
				//獲取參數的值
				dbus_message_iter_get_basic(&arg,&str);
				printf("recv param --: %s\n", str);
			}
			
		}
		else if(dbus_message_is_method_call(msg, "hello.world", "add"))
		{/////處理 add 遠程調用.
			DBusMessage *rp;
			DBusMessageIter r_arg;
			int a = 0;
			int b = 0;
			int sum = 0;
			printf("service: add  function\n");
 
			if(!dbus_message_iter_init(msg, &arg))
			{
				printf("no argument!\n");
				goto out;
			}
			if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_INT32)
			{
				printf("argument error\n");
				goto out;
			}
			dbus_message_iter_get_basic(&arg, &a);
 
			if(!dbus_message_iter_next(&arg))
			{
				printf("too few argument!\n");
				goto out;
			}
			//check argument type....
			dbus_message_iter_get_basic(&arg, &b);
			sum = a + b;
out:
			//new 一個迴應對象
			rp = dbus_message_new_method_return(msg);
			dbus_message_iter_init_append(rp, &r_arg);
			if(!dbus_message_iter_append_basic(&r_arg, DBUS_TYPE_INT32, &sum))
			{
				printf("no memory!!\n");
				return; 
			}
 
			//param3: 這個跟消息序列有關
			if(!dbus_connection_send(connection, rp, NULL))
			{
				printf("no memory!!\n");
				return;
			}
			dbus_connection_flush(connection);
			dbus_message_unref(rp);
		}
		//釋放空間
		dbus_message_unref(msg);
		send_signal(connection);
	}
	//dbus_bus_remove_match();
	
}
 
int main(int argc, char **argv)
{
	int ret = 0;
	DBusConnection *connection;
 
	connection = init_bus();
	if(connection == NULL)
	{
		printf("connect the dbus failed...\n");
		return -1;
	}
 
	handle_message(connection);
 
	return 0;
}

完整示例二

這應該就是使用C API爲D-BUS編寫一個簡單的服務器和客戶機所需要的全部。

下面的代碼片段來自dbus example.c。

接收信號:dbus-example receive

發送信號:dbus-example send param

監聽方法調用:dbus-example listen

調用方法:dbus-example query param

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/**
 * Connect to the DBUS bus and send a broadcast signal
 */
void sendsignal(char* sigvalue)
{
   DBusMessage* msg;
   DBusMessageIter args;
   DBusConnection* conn;
   DBusError err;
   int ret;
   dbus_uint32_t serial = 0;

   printf("Sending signal with value %s\n", sigvalue);

   // initialise the error value
   dbus_error_init(&err);

   // connect to the DBUS system bus, and check for errors
   conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Connection Error (%s)\n", err.message); 
      dbus_error_free(&err); 
   }
   if (NULL == conn) { 
      exit(1); 
   }

   // register our name on the bus, and check for errors
   ret = dbus_bus_request_name(conn, "test.signal.source", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Name Error (%s)\n", err.message); 
      dbus_error_free(&err); 
   }
   if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) { 
      exit(1);
   }

   // create a signal & check for errors 
   msg = dbus_message_new_signal("/test/signal/Object", // object name of the signal
                                 "test.signal.Type", // interface name of the signal
                                 "Test"); // name of the signal
   if (NULL == msg) 
   { 
      fprintf(stderr, "Message Null\n"); 
      exit(1); 
   }

   // append arguments onto signal
   dbus_message_iter_init_append(msg, &args);
   if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &sigvalue)) {
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }

   // send the message and flush the connection
   if (!dbus_connection_send(conn, msg, &serial)) {
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }
   dbus_connection_flush(conn);
   
   printf("Signal Sent\n");
   
   // free the message and close the connection
   dbus_message_unref(msg);
   dbus_connection_close(conn);
}

/**
 * Call a method on a remote object
 */
void query(char* param) 
{
   DBusMessage* msg;
   DBusMessageIter args;
   DBusConnection* conn;
   DBusError err;
   DBusPendingCall* pending;
   int ret;
   bool stat;
   dbus_uint32_t level;

   printf("Calling remote method with %s\n", param);

   // initialiset the errors
   dbus_error_init(&err);

   // connect to the system bus and check for errors
   conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Connection Error (%s)\n", err.message); 
      dbus_error_free(&err);
   }
   if (NULL == conn) { 
      exit(1); 
   }

   // request our name on the bus
   ret = dbus_bus_request_name(conn, "test.method.caller", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Name Error (%s)\n", err.message); 
      dbus_error_free(&err);
   }
   if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) { 
      exit(1);
   }

   // create a new method call and check for errors
   msg = dbus_message_new_method_call("test.method.server", // target for the method call
                                      "/test/method/Object", // object to call on
                                      "test.method.Type", // interface to call on
                                      "Method"); // method name
   if (NULL == msg) { 
      fprintf(stderr, "Message Null\n");
      exit(1);
   }

   // append arguments
   dbus_message_iter_init_append(msg, &args);
   if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &param)) {
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }
   
   // send message and get a handle for a reply
   if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) { // -1 is default timeout
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }
   if (NULL == pending) { 
      fprintf(stderr, "Pending Call Null\n"); 
      exit(1); 
   }
   dbus_connection_flush(conn);
   
   printf("Request Sent\n");
   
   // free message
   dbus_message_unref(msg);
   
   // block until we recieve a reply
   dbus_pending_call_block(pending);

   // get the reply message
   msg = dbus_pending_call_steal_reply(pending);
   if (NULL == msg) {
      fprintf(stderr, "Reply Null\n"); 
      exit(1); 
   }
   // free the pending message handle
   dbus_pending_call_unref(pending);

   // read the parameters
   if (!dbus_message_iter_init(msg, &args))
      fprintf(stderr, "Message has no arguments!\n"); 
   else if (DBUS_TYPE_BOOLEAN != dbus_message_iter_get_arg_type(&args)) 
      fprintf(stderr, "Argument is not boolean!\n"); 
   else
      dbus_message_iter_get_basic(&args, &stat);

   if (!dbus_message_iter_next(&args))
      fprintf(stderr, "Message has too few arguments!\n"); 
   else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args)) 
      fprintf(stderr, "Argument is not int!\n"); 
   else
      dbus_message_iter_get_basic(&args, &level);

   printf("Got Reply: %d, %d\n", stat, level);
   
   // free reply and close connection
   dbus_message_unref(msg);   
   dbus_connection_close(conn);
}

void reply_to_method_call(DBusMessage* msg, DBusConnection* conn)
{
   DBusMessage* reply;
   DBusMessageIter args;
   bool stat = true;
   dbus_uint32_t level = 21614;
   dbus_uint32_t serial = 0;
   char* param = "";

   // read the arguments
   if (!dbus_message_iter_init(msg, &args))
      fprintf(stderr, "Message has no arguments!\n"); 
   else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args)) 
      fprintf(stderr, "Argument is not string!\n"); 
   else 
      dbus_message_iter_get_basic(&args, &param);

   printf("Method called with %s\n", param);

   // create a reply from the message
   reply = dbus_message_new_method_return(msg);

   // add the arguments to the reply
   dbus_message_iter_init_append(reply, &args);
   if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &stat)) { 
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }
   if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &level)) { 
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }

   // send the reply && flush the connection
   if (!dbus_connection_send(conn, reply, &serial)) {
      fprintf(stderr, "Out Of Memory!\n"); 
      exit(1);
   }
   dbus_connection_flush(conn);

   // free the reply
   dbus_message_unref(reply);
}

/**
 * Server that exposes a method call and waits for it to be called
 */
void listen() 
{
   DBusMessage* msg;
   DBusMessage* reply;
   DBusMessageIter args;
   DBusConnection* conn;
   DBusError err;
   int ret;
   char* param;

   printf("Listening for method calls\n");

   // initialise the error
   dbus_error_init(&err);
   
   // connect to the bus and check for errors
   conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Connection Error (%s)\n", err.message); 
      dbus_error_free(&err); 
   }
   if (NULL == conn) {
      fprintf(stderr, "Connection Null\n"); 
      exit(1); 
   }
   
   // request our name on the bus and check for errors
   ret = dbus_bus_request_name(conn, "test.method.server", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Name Error (%s)\n", err.message); 
      dbus_error_free(&err);
   }
   if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) { 
      fprintf(stderr, "Not Primary Owner (%d)\n", ret);
      exit(1); 
   }

   // loop, testing for new messages
   while (true) {
      // non blocking read of the next available message
      dbus_connection_read_write(conn, 0);
      msg = dbus_connection_pop_message(conn);

      // loop again if we haven't got a message
      if (NULL == msg) { 
         sleep(1); 
         continue; 
      }
      
      // check this is a method call for the right interface & method
      if (dbus_message_is_method_call(msg, "test.method.Type", "Method")) 
         reply_to_method_call(msg, conn);

      // free the message
      dbus_message_unref(msg);
   }

   // close the connection
   dbus_connection_close(conn);
}

/**
 * Listens for signals on the bus
 */
void receive()
{
   DBusMessage* msg;
   DBusMessageIter args;
   DBusConnection* conn;
   DBusError err;
   int ret;
   char* sigvalue;

   printf("Listening for signals\n");

   // initialise the errors
   dbus_error_init(&err);
   
   // connect to the bus and check for errors
   conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Connection Error (%s)\n", err.message);
      dbus_error_free(&err); 
   }
   if (NULL == conn) { 
      exit(1);
   }
   
   // request our name on the bus and check for errors
   ret = dbus_bus_request_name(conn, "test.signal.sink", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Name Error (%s)\n", err.message);
      dbus_error_free(&err); 
   }
   if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
      exit(1);
   }

   // add a rule for which messages we want to see
   dbus_bus_add_match(conn, "type='signal',interface='test.signal.Type'", &err); // see signals from the given interface
   dbus_connection_flush(conn);
   if (dbus_error_is_set(&err)) { 
      fprintf(stderr, "Match Error (%s)\n", err.message);
      exit(1); 
   }
   printf("Match rule sent\n");

   // loop listening for signals being emmitted
   while (true) {

      // non blocking read of the next available message
      dbus_connection_read_write(conn, 0);
      msg = dbus_connection_pop_message(conn);

      // loop again if we haven't read a message
      if (NULL == msg) { 
         sleep(1);
         continue;
      }

      // check if the message is a signal from the correct interface and with the correct name
      if (dbus_message_is_signal(msg, "test.signal.Type", "Test")) {
         
         // read the parameters
         if (!dbus_message_iter_init(msg, &args))
            fprintf(stderr, "Message Has No Parameters\n");
         else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args)) 
            fprintf(stderr, "Argument is not string!\n"); 
         else
            dbus_message_iter_get_basic(&args, &sigvalue);
         
         printf("Got Signal with value %s\n", sigvalue);
      }

      // free the message
      dbus_message_unref(msg);
   }
   // close the connection
   dbus_connection_close(conn);
}

int main(int argc, char** argv)
{
   if (2 > argc) {
      printf ("Syntax: dbus-example [send|receive|listen|query] [<param>]\n");
      return 1;
   }
   char* param = "no param";
   if (3 >= argc && NULL != argv[2]) param = argv[2];
   if (0 == strcmp(argv[1], "send"))
      sendsignal(param);
   else if (0 == strcmp(argv[1], "receive"))
      receive();
   else if (0 == strcmp(argv[1], "listen"))
      listen();
   else if (0 == strcmp(argv[1], "query"))
      query(param);
   else {
      printf ("Syntax: dbus-example [send|receive|listen|query] [<param>]\n");
      return 1;
   }
   return 0;
}

示例編譯

Linux編譯

gcc dbus_test.c -o dbus_test -ldbus-1 -lpthread -L/home/share/opensource/dbus/lib -I/home/share/opensource/dbus/include/dbus-1.0

ARM編譯

arm-none-linux-gnueabi-gcc dbus_test.c -o dbus_test -ldbus-1 -lpthread -L/home/share/opensource/dbus_ql/lib -I/home/share/opensource/dbus_ql/include/dbus-1.0

有可能會提示找不dbus-arch-deps.h頭文件,在系lib\dbus-1.0\include,然後拷貝到include/dbus-1.0/dbus目錄

運行注意事項

需要先將dbus-daemon 運行起來。在ARM中查看進程,如下則已運行,否則需要先啓動daemon。

# ps | grep dbus
  812 messageb   0:00 /usr/bin/dbus-daemon --system
 1210 root     310:00 /usr/bin/dbus-daemon --fork --print-pid 4 --print-address 6 --session

啓動dbus-daemon

啓動session:

./dbus-daemon --config-file=/etc/dbus-1/session.conf –print-address

設置環境變量DBUS_SESSION_BUS_ADDRESS,將打印的內容設置成DBUS_SESSION_BUS_ADDRESS的值就可以了,這樣session-bus就可以使用了。

啓動system:

./dbus-daemon --config-file=/etc/dbus-1/system.conf  --print-address

打印的大致內容是:

unix:path=/var/run/dbus/system_bus_socket,guid=73e31e28f60060659d6ee6005422cb1d

設置環境變量DBUS_SYSTEM_BUS_ADDRESS,需將打印前半部的內容設置成環境變量DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket,重啓設備或source配置文件一下。

在ARM中運行若提示,則session沒有啓動,報錯:

Using X11 for dbus-daemon autolaunch was disabled at compile time, set your DBUS_SESSION_BUS_ADDRESS instead

解決方案參考下面鏈接:

https://stackoverflow.com/questions/41242460/how-to-export-dbus-session-bus-address

export $(dbus-launch)

通過dbus-launch程序,會fork一個session回話的daemon,程序即可通過該daemon進行通信。

或者(我是使用的這種)。

eval `dbus-launch --auto-syntax`

要連接到session-bus的程序必須知道DBUS_SESSION_BUS_ADDRESS的值,因爲DBUS_SESSION_BUS_ADDRESS的值不是唯一的,每次都不一樣,每次都要設置DBUS_SESSION_BUS_ADDRESS的環境變量,雖然可以通過./dbus-launch來啓動dbus-daemon,並且dbus-launch自帶設置DBUS_SESSION_BUS_ADDRESS的環境變量的功能,但由於dbus-launch設置的環境變量只在本進程,而且是本次執行中有效。所以一般要通過上述命令來啓動dbus-launch,該命令採用eval來執行兩次,第一次執行dbus-lauch --auto-syntax,除了啓動dbus daemon之外,還輸出了下面的內容:

DBUS_SESSION_BUS_ADDRESS='unix:path=/tmp/dbus-6Z62FMmwf3,guid=5dbd92e4865a3f56880d2120000000d6';
export DBUS_SESSION_BUS_ADDRESS; DBUS_SESSION_BUS_PID=998;

第二次執行時就將環境變量DBUS_SESSION_BUS_ADDRESS暴露出去了。之後你就可以啓動要連接到session-bus的程序。

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