Dbus-glib使用心得

Dbus-glib使用心得

 

一、背景介紹

從安全的角度考慮,廣泛的使用DBUS進行進程間通訊。

1.優點:

DBUS總線分爲系統總線與會話總線兩類,兩者之前不能互相通信,所以任何應用程序不能欺騙系統事件,安全性很好。

2.缺點

直接使用Dbus標準接口調用很繁瑣,各服務之間各寫一套,不易維護也容易出錯。

接受方法調用端、消息接收端等程序需要非阻塞式(阻塞式的無法多線程DBUS通訊)判斷是否接收到DBUS信息,形如:

While(1)

{

dbus_connection_read_write();

msg = dbus_connection_borrow_message(conn);

if (NULL == msg) {

  usleep(xxx);

continue;

}

}

如上所示,多個服務同時運行的情況下,會佔用大量CPU時間片,之前就有測試報告應用程序壓力運行單一操作的情況下,應用程序會由快跑慢。

因此需要一個穩定可靠的DBUS調用封裝,上層統一該封裝接口進行DBUS通訊。

二、Dbus-glib介紹

Dbus-glib是GNU標準庫,在Dbus接口上封裝,方便上層服務與應用更好的使用。其形如一個DBUS代理服務器,由它進行所有DBUS消息的遍歷與轉發,服務端與消息發送端只需要向DBUS deamon申請註冊唯一的DBUS name 、綁定GOBJECT後,DBUS deamon就會將申請連到到該DBUS name的DBUS信息轉發給指定應用。

直接調用DBUS接口的構圖如下:

 

使用Dbus-glib結構圖如下:

 

函數調用流程:

服務端申請一個GObject,綁定以下信息:

Dbus name:A,

Dbus object:B

Dbus interface:C

Method :D

註冊到dbus daemon中,其中D設置爲回調函數

客戶端向dbus daemon申請調用註冊信息爲

Dbus name:A,

Dbus object:B

Dbus interface:C

的D函數

dbus daemon收到客戶端的消息後,查詢是否存在該註冊信息的回調函數,如果找不到daemon會產生錯誤消息,作爲應答消息給客戶端找到則且執行該回調函數,將結果返回給客戶端。

消息發送流程:

1.消息發送端申請一個GObject,綁定以下信息:

Dbus name:A,

Dbus object:B

Dbus interface:C

signal :D

註冊到dbus daemon中

2.消息接收端向dbus daemon申請綁定註冊信息爲

Dbus name:A,

Dbus object:B

Dbus interface:C

Signal 爲D的消息回調函數

dbus daemon收到消息發送端發出的DBUS消息後,查詢是否存在該消息的綁定回調函數,且執行該回調函數。

所以消息發送端是不知道誰是接收端的,這個也與DBUS底層接口實現方式不同

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

三、通過Dbus-glib寫一個服務端

dbus-glib定義向dbus daemon申請一個註冊信息的形式爲GObject(C語言)的對

1.寫一個XML

首先,先學習怎麼使用內置的xml文件自動創建出易於使用的dbus代理對象。如下的一個xml文件描述了了一個名爲“HelloWorld”,輸入參數爲char *,輸出參數爲char*[]的被調用的函數。

 

 dbus的接口描述文件統一採用utf-8編碼。type域數據類型定義如下

a

ARRAY 數組

b

BOOLEAN 布爾值

d

DOUBLE IEEE 754雙精度浮點數

g

SIGNATURE 類型簽名

i

INT32 32位有符號整數

n

INT16 16位有符號整數

o

OBJECT_PATH 對象路徑

q

UINT16 16位無符號整數

s

STRING 零結尾的UTF-8字符串

t

UINT64 64位無符號整數

u

UINT32 32位無符號整數

v

VARIANT 可以放任意數據類型的容器,數據中包含類型信息。例如glib中的GValue。

x

INT64 64位有符號整數

y

BYTE 8位無符號整數

()

定義結構時使用。例如"(i(ii))"

{}

定義鍵-值對時使用。例如"a{us}"

a表示數組,數組元素的類型由a後面的標記決定。例如:

"as"是字符串數組。 

數組"a(i(ii))"的元素是一個結構。用括號將成員的類型括起來就表示結構了,結構可以嵌套。 

數組"a{sv}"的元素是一個鍵-值對。"{sv}"表示鍵類型是字符串,值類型是VARIANT。 

一個node可以有多個interface ,一個interface可以有多個method或signal,上例只以簡單的單函數來說明,如果多個函數可以寫成:

<node name="/">

  <interface name="xxxx">

    <method name="xx">

      <arg type="s"/>

      <arg type="as" direction="out"/>

</method>

   <method name="xxxx">

   </method>

  </interface>

</node>

2.通過dbus-binding-tool生成頭文件

dbus-binding-tool目前在92服務器上就有,可以直接運行,執行指令:

dbus-binding-tool --mode=glib-server --prefix=your_module_name your_server.xml > xxx_stub.h

"--prefix"參數定義了對象前綴。設對象前綴是$(prefix),則生成的DBusGObjectInfo結構變量名就是 dbus_glib_$(prefix)_object_info。綁定文件會爲接口方法定義回調函數。回調函數的名稱是這樣的:首先將xml中的方法名稱轉換到全部小寫,下劃線分隔的格式,然後增加前綴"$(prefix)_"。例如:如果xml中有方法SendMessage,綁定文件就會引用一個名稱爲$(prefix)_send_message的函數。

如上例中,--prefix = some_object 生成的general_stub.h中,DBusGObjectInfo結構變量名dbus_glib_some_object_object_info,回調函數名爲some_object_hello_world

生成的general_stub.h不需要手動修改,直接使用。

3.創建對象

dbus-glib用GObject實現dbus對象,所以我們首先要實現一個對象,繼承GObject,以下說明請參考提供的示例demo代碼。

3.1 定義對象

typedef struct SomeObject

{

  GObject parent;

}SomeObject;

typedef struct SomeObjectClass

{

  GObjectClass parent;

}SomeObjectClass;

在 GObject ,類是兩個結構體的組合,一個是實例結構體,另一個是類結構體,上例中SomeObject是實例結構體SomeObjectClass是類結構體

命名爲XXX、XXXClass形式.

3.2實現類類型的定義

G_DEFINE_TYPE(SomeObject, some_object, G_TYPE_OBJECT)

G_DEFINE_TYPE 可以讓 GObject 庫的數據類型系統能夠識別我們所定義的 SomeObject 類類型,它接受三個參數,第一個參數是類名,即 SomeObject;第二個參數則是類的成員函數(面向對象術語稱之爲方法行爲)名稱的前綴,例如 some_object _get_type 函數即爲 SomeObject 類的一個成員函數, some_object” 是它的前綴;第三個參數則指明 SomeObject類類型的父類型爲 G_TYPE_OBJECT

3.3聲明類的函數

GType some_object_get_type (void);

#define SOME_TYPE_OBJECT              (some_object_get_type ())

static void some_object_init (SomeObject *obj)

{

}

static void some_object_class_init (SomeObjectClass *klass)

{

}

some_object_get_type函數的作用是向 GObject 庫所提供的類型管理系統提供要註冊的SomeObject類類型的相關信息,可以不實現,但必須要聲明。

some_object_init 是類成員的構造函數

some_object_class_init 是類結構的構造函數,與類成員構造函數區別在於,該構造函數只在該類定義時運行一次,常用來進行消息信號的初始化等。而some_object_init則在創建成員時都會調用一次(如obj = g_object_new

上例中通過G_DEFINE_TYPE(SomeObject, some_object, G_TYPE_OBJECT)第二個參數把類成員定爲some_object,所以其成員函數名爲:

some_object_get_type

some_object_init

some_object_class_init

所以如果G_DEFINE_TYPE第二個參數爲“XXX,則相應的成員函數就是XXX_get_type、XXX_init

至此對象創建完成。

4.向dbus deamon申請註冊

g_type_init ();

dbus_g_object_type_install_info (SOME_TYPE_OBJECT, &dbus_glib_some_object_object_info);

mainloop = g_main_loop_new (NULL, FALSE);

dbus_g_object_type_install_info的作用是向dbus-glib登記對象信息,dbus_glib_some_object_object_info是哪來的?是由前面XML生成的頭文件中指定:

const DBusGObjectInfo dbus_glib_some_object_object_info = {

}

g_main_loop_new用來申請創建一個主循環,接收DBUS消息,用於服務端、消息接收端

bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); 

  if (!bus)

    lose_gerror ("Couldn't connect to session bus", error);

申請一個會話總線

  bus_proxy = dbus_g_proxy_new_for_name (bus, "org.freedesktop.DBus",

 "/org/freedesktop/DBus",

 "org.freedesktop.DBus");

創建連接到dbus daemon

org.freedesktop.DBus ----   dbus daemonDBUS名

/org/freedesktop/DBus ---  dbus daemon對象名

org.freedesktop.DBus  ---  dbus daemon interface

  if (!dbus_g_proxy_call (bus_proxy, "RequestName", &error,

  G_TYPE_STRING, "org.designfu.SampleService",

  G_TYPE_UINT, 0,

  G_TYPE_INVALID,

  G_TYPE_UINT, &request_name_result,

  G_TYPE_INVALID))

lose_gerror ("Failed to acquire org.designfu.SampleService", error);

調用dbus daemon的函數“RequestName”,申請一個DBUS名爲“org.designfu.SampleService”的註冊信息

  obj = g_object_new (SOME_TYPE_OBJECT, NULL);

  dbus_g_connection_register_g_object (bus, "/SomeObject", G_OBJECT (obj));

申請之前定義的一個對象SomeObject,將該對象與bus綁定,“/SomeObject”是method的頂層對象路徑

  g_main_loop_run (mainloop);

進入loop循環,DBUS服務器完成,信息如下:

Dbus name: org.designfu.SampleService

Dbus object: /SomeObject

Dbus interface:test.method.Type

Method :HelloWorld

5.實現method

general_stub.h中查詢method直實名稱,如

static const DBusGMethodInfo dbus_glib_some_object_methods[] = {

  { (GCallback)

 some_object_hello_world, dbus_glib_marshal_some_object_BOOLEAN__STRING_POINTER_POINTER, 0 },

};

即函數名爲some_object_hello_world,函數的第一個輸入參數固定爲對象實例的指針最後一個參數必是GError **,中間爲用戶自定的參數,如本例中聲明如下

gboolean

some_object_hello_world (SomeObject *obj, const char *hello_message, char ***ret, GError **error)

最後在代碼中完成該函數實現。

注意:函數聲明要寫在#include "general_stub.h"之前,否則編繹不識別some_object_hello_world

以上這些跟對象相關的部分比較繁瑣,不理解的情況下也可以把它當作公式一樣記下來,只需要修改自定義的部分,其它照搬就可以了。

四、通過Dbus-glib寫一個客戶端

客戶端實現較服務端簡單,並不需要像DBUS底層庫調用那樣申請自身的DBUS名等。

g_type_init ();

申請一個會話總線

  bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);

  if (!bus)

    lose_gerror ("Couldn't connect to session bus", error);

向dbus deamon申請連接到以下信息的DBUS總線上

  remote_object = dbus_g_proxy_new_for_name (bus,

     "org.designfu.SampleService",

     "/SomeObject",

     " test.method.Type ");

調用HelloWorld程序

  if (!dbus_g_proxy_call (remote_object, "HelloWorld", &error,

  G_TYPE_STRING, "Hello from example-client.c!", G_TYPE_INVALID,

  G_TYPE_STRV, &reply_list, G_TYPE_INVALID))

lose_gerror ("Failed to complete HelloWorld", error);

使用完成後釋放返回參數,此例中返回值是一個字符串數組

  g_strfreev (reply_list);

釋放與目標DBUS的連接,結束

g_object_unref (G_OBJECT (remote_object));

五、消息發送與接收

消息發送端

消息發送與服務端實現基本相同,只需要在類結構構造函數中增加對信號的初始化。

XML文件中增加<signal name =“xxx />,以下是一個同時具備接受函數調用與發送信號功能XML文件示例

 

代碼中在類結構構造函數中初始化該信號

static void

some_object_class_init (SomeObjectClass *klass)

{

  signals[0] =

    g_signal_new ("say_hi",

  G_OBJECT_CLASS_TYPE (klass),

                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,

                  0,

                  NULL, NULL,

                  g_cclosure_marshal_VOID__STRING,

                  G_TYPE_NONE, 1, G_TYPE_UINT);

}

g_signal_new中的其它參數在簡單功能不會用到,我們只需要使用第一個和最後兩個參數。

第一個參數是信號的名字,與XML文件匹配,此處注意,XML中是“SayHi”,在此處信號名會被轉換爲“say_hi”,規則基本就是大寫轉小寫、單詞間加下劃線,且第一個字符必須是字母。所以如果之前信號名叫PMlevel的話,建議改名爲PmLevel,或者將g_signal_new中寫成“p_mlevel”,否則運行會報關連錯誤。XML中如果信號名爲全小寫,則不需要轉換。

最後兩個參數表示消息包含的數據個數與數據類型,如上例中表示該消息包含一個UINT型的數據。

發送消息

Level = 1;  

g_signal_emit (obj, signals[0], 0, level);

所以消息發送端只將消息發送給dbus daemon,而由dbus daemon查詢誰對該消息有響應,則將該消息發送給指定的進程。

消息接收端

消息接收端與客戶端實現也基本相同,區別在於消息接收端處於loop循環接收狀態,且需要綁定接收消息後的回調函數。

g_type_init ();

mainloop = g_main_loop_new (NULL, FALSE);

bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);

if (!bus)

  lose_gerror ("Couldn't connect to session bus", error);

向dbus deamon申請連接到以下信息的DBUS總線上,消息由該總線發出

  remote_object = dbus_g_proxy_new_for_name (bus,

     "org.designfu.SampleService",

     "/SomeObject",

     "test.server");

設置接收到消息後的回調函數

  dbus_g_proxy_add_signal (remote_object, "SayHi", G_TYPE_UINT, G_TYPE_INVALID);

  dbus_g_proxy_connect_signal (remote_object, "SayHi", G_CALLBACK (receive_signal_handler),NULL, NULL);

進入LOOP

  g_main_loop_run (mainloop);

當收到SayHi的DBUS消息時,客戶端自動執行回調函數receive_signal_handler

demo已經將函數調用與消息收發寫在了一起,流程爲:

客戶端定時2秒調用一次服務器端的函數HelloWord

服務端HelloWord發送消息SayHi,附帶整型數據1

客戶端收到消息後,消息回調函數打印出收到的數據

六、效率改進

以上幾點大致說明了dbus-glib使用方法,但其在使用上還是有不方便的地方:

1 一個API要定義一個xml接口描述

2 數據封裝非常複雜,非常不利於以後接口的擴展

爲了克服上面的缺點,提高可擴展性和效率,可以這樣做:
如果一個應用分爲client,server兩端的話,要高效率的實現client/server之間
的通信,可以採用如下方式:

改進一:定義一個通用的API xml 接口描述

<?xml version="1.0" encoding="UTF-8" ?>

<node name=" ="/org/freedesktop/DBus/General_api">

  <interface name=" org.freedesktop.DBus.general_api">

    <method name=" client_request">

      <annotation name="org.pmonitor.DBus.GLib.request" value="_ client_request_cb"/>

      <arg type="i" name="action_id" direction="in" />  

    <arg type="ay" name="input_garray" direction="in" />  

    <arg type="ay" name="output_garray" direction="out" />

      <arg type="i" name="result" direction="out" />

    </method>

  </interface>

</node>

Ay表示字節的數組,其數據類型爲GArray  這個通用的模板關鍵之處就是這個Garray, Garray本身是個容器,這個容器裏面可以裝任何東西。我們就是利用這個GArray來實現client與server之間數據的傳遞,無論想傳遞什麼要的數據

額外的參數類型支持如下:

D-Bus type signature

Description

GType

C typedef

Free function

Notes

as

Array of strings

G_TYPE_STRV

char **

g_strfreev

 

v

Generic value container

G_TYPE_VALUE

GValue *

g_value_unset

The calling conventions for values expect that method callers have
allocated return values; see below.

數組類型集合。

D-Bus type signature

Description

GType

C typedef

Free function

Notes

ay

Array of bytes

DBUS_TYPE_G_BYTE_ARRAY

GArray *

g_array_free

 

au

Array of uint

DBUS_TYPE_G_UINT_ARRAY

GArray *

g_array_free

 

ai

Array of int

DBUS_TYPE_G_INT_ARRAY

GArray *

g_array_free

 

ax

Array of int64

DBUS_TYPE_G_INT64_ARRAY

GArray *

g_array_free

 

at

Array of uint64

DBUS_TYPE_G_UINT64_ARRAY

GArray *

g_array_free

 

ad

Array of double

DBUS_TYPE_G_DOUBLE_ARRAY

GArray *

g_array_free

 

ab

Array of boolean

DBUS_TYPE_G_BOOLEAN_ARRAY

GArray *

g_array_free

 

Hash表數組:

D-Bus type signature

Description

GType

C typedef

Free function

Notes

a{ss}

Dictionary mapping strings to strings

DBUS_TYPE_G_STRING_STRING_HASHTABLE

GHashTable *

g_hash_table_destroy

 

改進二:用dbus的工具函數生成proxy頭文件

前面有說到使用dbus-binding-tool將XML腳本轉換爲stub.h供服務器使用

 dbus-binding-tool --mode=glib-server --prefix=your_module_name dbus_general.xml > general_stub.h

其實將--mode值修改爲glib-client,生成proxy.h文件可供客戶端使用,如:
dbus-binding-tool --mode=glib-client --prefix=your_module_name dbus_general.xml > general_proxy.h

打開general_proxy.h看到服務端提供的HelloWorld已經轉爲

test_server_hello_world (DBusGProxy *proxy, const char * IN_arg0, char *** OUT_arg1, GError **error)

客戶端可以直接調用test_server_hello_world,而不再需要使用dbus_g_proxy_call來調用該函數。

general_proxy.h中的另兩個函數聲明涉及異步調用,暫未使用到,沒有了解

phoenix中的pms與libpms就是使用統用API實現多函數調用的例子。

七、注意事項

1.向dbus deamon註冊DBUS的名稱是可以重複的:

dbus_g_proxy_call (bus_proxy, "RequestName", &error,

  G_TYPE_STRING, "org.designfu.SampleService",

  G_TYPE_UINT, 0,

  G_TYPE_INVALID,

  G_TYPE_UINT, &request_name_result,

  G_TYPE_INVALID)

比如demo中的dbus_server程序可以連續運行多次,通過dbus-monitor session可以看到其分配的實際Bus Names是不同的,比如一個是“1.5”,一個是“1.6”,因於“1.5”先創建,所以其處於dbus deamon同名隊列的頂部,所以當客戶端發起DBUS通訊時,就只有“1.5”有效,當“1.5”退出後,“1.6”纔會進入DBUS通訊狀態。

2.函數調用中的out參數,如果是服務端申請的內存空間,客戶端在使用完後,要記得釋放內存。

如g_new -----g_free等,具體數據類型不同,其對應的申請內存與釋放內存的接口不同,詳細請查看http://developer.gnome.org/glib/stable/

3.客戶端等完成函數調用等,要關閉DBUS連接:

remote_object = dbus_g_proxy_new_for_name ();

  g_object_unref (G_OBJECT (remote_object));

dbus_g_bus_get()

dbus_g_connection_unref(bus);

4.服務器端被調函數(method)對應的函數返回值是gboolean類型,實際程序運行成功或失敗要通過輸出參數返回,不能通過函數返回。函數實現中一定要返回TRUE,否則dbus daemon 不會向客戶端回複函數調用結果。

八、多線程防衝突

以上demo中實現方法只是單線程實現dbus調用。如果多線程的情況下,以及庫函數情況下,爲確保不同線程使用不同的DBusConnection,在創建dbus總線時要注意使用關鍵字創建各自私有的總線:

Method客戶端實現:

GMainContext* main_context = NULL;

main_context = g_main_context_new();//申請獨立的context

/*不使用dbus_g_bus_get*/

bus = dbus_g_bus_get_private(DBUS_BUS_SESSION,main_context,&error);

dbus_g_connection_close(bus);//私有的總線連接要先close才能unref

dbus_g_connection_unref(bus);

g_main_context_unref(main_context);

注意 :

1. g_main_context_unref()要與g_main_context_new() 配合使用,如果申請的資源未釋放,會導致文件句柄泄露。

2. dbus_g_bus_get_private申請的私有總線連接在使用完成後,要使用dbus_g_connection_close先關閉連接後再釋放資源dbus_g_connection_unref, 否則只調用dbus_g_connection_unref會報“私有連接無法關閉”,導致內存泄露。 

dbus_g_connection_close目前glib庫並未封裝,需要自已封裝一個,方法如下:

#define  _DBUS_POINTER_UNSHIFT(p) ((void*) (((char*)p) - sizeof (void*)))

#define DBUS_CONNECTION_FROM_G_CONNECTION(x)    ((DBusConnection*) _DBUS_POINTER_UNSHIFT(x))

void dbus_g_connection_close( DBusGConnection *  connection )

{

return dbus_connection_close(DBUS_CONNECTION_FROM_G_CONNECTION(connection));

}   

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