Tout d’abord, on peut se demander ce qu’est DBus [1]. Il est vrai que les environnements de bureau modernes, tels que Gnome et KDE 4 ou encore des applications telles que Pidgin, OpenWengo et autres, utilisent cette technologie. Cet article a pour but de vous faire découvrir les concepts sous-jacents de DBus, mais aussi de vous montrer par l’exemple comment les utiliser.
L’évolution des exemples sera très simple :
écriture d’une première application permettant de récupérer la liste des services existant sur la boucle locale ;
écriture d’un premier service DBus en C ;
écriture d’un premier client en Python ;
évolution des deux programmes écrits précédemment.
Le lecteur pourra s’exercer de plus en utilisant les interfaces écrites par le logiciel Pidgin [2] : notification de connexion, envoi de messages... Un petit exercice serait d’avoir une petite application qui informe de l’arrivée d’un nouveau message, du nombre de contacts connectés... Les possibilités ne sont limitées que par l’imagination.
|
|
|
1.1 Qu’est-ce-que DBus ?
|
DBus est un projet open source de communication inter-processus (IPC en anglais) faisant partie du projet énormément plus important qu’est freedesktop.org. En plus de la communication inter-processus, DBus est aussi un mécanisme d’appel de procédures distantes (RPC : remote procedure call en anglais) tout comme Corba peut l’être. DBus se veut être un remplaçant, pour les projets Gnome et Kde, de leurs modèles de composants respectifs, à savoir [3] et [4]. DBus est donc né dans un souci d’unification des modèles de composants orientés bureau. Bien que DBus soit un middleware orienté communication inter-processus, en aucun cas ce projet ne veut concurrencer les mécanismes de bas niveau tels que les sockets, la mémoire partagée ou encore les queues de messages. Il manque à DBus une quantité importante de fonctionnalités que l’on peut retrouver dans Corba, mais ces choix sont volontaires dans un souci de rapidité et de simplicité. Et d’un point de vue purement fonctionnel, DBus reste à haut niveau du point de vue de l’utilisateur en proposant des fonctionnalités telles que :
noms de domaine structurés ;
appel de procédures à distance avec gestion des exceptions (point non traité dans cet article) ;
un mécanisme générique de gestion de distribution de signaux ;
séparation très claire entre service " système " et " session " ;
indépendant du langage de programmation.
D’un point de vue protocolaire applicatif, DBus utilise un protocole de communication binaire (comprendre " non textuel ") dont la description est disponible sur le site de freedesktop.org [5]. La communication de DBus est basée sur un système de messages. Et comme le spécifie le document protocolaire fourni sur freedesktop.org, DBus est un système à faible latence, à faible coût (car utilisant des messages binaires et non des formats comme XML nécessitant une lecture plus ou moins complexe du message) et très facile d’utilisation (fonctionnement en termes de messages et non de flux d’octets).
DBus, à l’origine, a été conçu pour qu’une application puisse communiquer avec une seule autre, c’est-à -dire qu’il y avait une relation " 1-1 " (one-to-one), mais, qu’aujourd’hui, il existe ce qui est appelé dans la terminologie DBus des " bus de communication ". Ces bus de communication permettent d’avoir une relation " n-m " : n applications communiquant avec m autres. C’est le sujet de la section suivante.
|
|
|
1.2 Les bus de communication
|
Ils sont utilisés lors d’une communication " n-m ". L’architecture proposée est de passer par un démon qui s’occupe de faire le routage de tous les messages vers les différents destinataires. Voilà un diagramme issu du site officiel de DBus décrivant le mécanisme :
 |
|
Figure 1 : Diagramme de fonctionnement du démon DBus
|
Expliquer le fonctionnement interne de DBus n’est pas le but de cet article, mais nous allons quand même décortiquer le diagramme ci-dessus.
Le cas présenté ci-dessus est un cas où un processus passe par le démon DBus pour dialoguer avec un second processus. On remarque que chacun des processus détient une instance de connexion au démon et vice-versa : le démon a connaissance de tous les processus voulant communiquer (comme un routeur vous me direz). Chaque processus peut exporter des interfaces et récupérer d’autres processus par un mécanisme de proxies expliqué plus loin dans l’article.
On remarque que le démon s’occupe de faire la liaison entre le nom d’une interface et le processus qui l’a déclaré afin de bien router tous les messages, et qu’en cas de signal il diffuse à tous les abonnés (cf. ci-après).
Voilà pour l’explication générale du démon DBus. Maintenant, côté utilisation, DBus définit deux types de bus :
Le bus système : c’est un bus de messages partant du noyau jusqu’aux services globaux. Par exemple, si une application quelconque veut être notifiée lorsqu’un nouveau périphérique est ajouté sur la machine, il y a de grandes chances, voire obligation, de passer par le bus système (si on veut respecter la logique du middleware).
Les bus session : ce sont un ou plusieurs bus de communication pour chaque session graphique (à chaque fois qu’un utilisateur se connecte sur son environnement de bureau). Rien n’interdit à un programme de créer son propre bus et de ne pas le partager, mais ça n’a pas de sens vis-à -vis de DBus qui se veut être un mécanisme de partage de données à travers de multiples applications.
Pour terminer sur les différents bus, il est intéressant de savoir que les messages sur un bus ne sont pas envoyés sur les autres bus existants, mais que rien n’empêche chaque application de se connecter à de multiples bus. On peut rajouter de plus que DBus définit des politiques de sécurité pour l’accès à différents services (que le lecteur pourra bien sûr configurer), faire du lancement sur demande de services,... Ces points seront abordés plus en détail dans la suite de l’article.
|
|
|
1.3 L’adressage dans DBus
|
Afin qu’un processus P1 puisse joindre un processus destinataire P2, il va avoir besoin de plusieurs informations :
1. Le bus sur lequel P2 s’est inscrit. Normalement, un bus de communication est ouvert durant toute la vie d’un processus.
2. Le nom " bien connu " du service, un peu comme le service fourni par le protocole DNS. Ce nom est composé uniquement de caractères alphabétiques. Afin de réduire les collisions de noms, la règle de nommage reprend la notion de " paquetage " en Java. Par exemple : org.linuxmag.sample.DummyObject.
3. Un service peut encapsuler plusieurs objets. Afin de les différencier, il y a donc un chemin d’objet à spécifier. Le chemin de l’objet reprend la notation Unix des chemins d’une arborescence. Par exemple : /org/linuxmag/sample/DummyObject. Par convention, le chemin de l’objet reprend les éléments de base du nom de service et ajoute, à la fin, un élément propre à cet objet. Mais cette convention n’est que " virtuelle ", chacun est libre de faire ce qu’il veut.
4. Afin de supporter le paradigme de la programmation orientée objet où ce sont les objets qui fournissent des services, il existe une autre unité de nommage : l’interface. L’interface définit la liste des méthodes avec leurs paramètres d’entrée et de sortie, la liste des possibles signaux. Il est toutefois aussi possible de réutiliser une même interface entre plusieurs objets implémentant le même service. Un exemple est l’interface org.freedesktop.DBus.Introspectable qui définit l’introspection (connaître la description d’un objet : liste des interfaces qu’il implémente, liste des méthodes, ...). En utilisant, le binding GLib/DBus, tous les objets générés supportent cette interface.
Il reste un autre terme à éclaircir avant de passer à la pratique, c’est celui de proxy. Un proxy n’est ni plus ni moins qu’une référence sur un objet distant, et la façon dont l’utilisateur " perçoit " cette entité est totalement dépendante du binding utilisé. En C++, Python ou Java, il est totalement transparent. En effet, le développeur récupère cette référence et appelle la méthode qu’il souhaite. Cependant, avec un binding tel que GLib, le proxy n’est pas transparent comme on peut le voir dans les exemples ci-après.
De plus, certaines implémentations du proxy tiennent compte du failover. C’est-à -dire que si un client obtient un proxy et que le service s’arrête et redémarre, le proxy est conservé sans aucune modification à faire.
Après ce petit passage légèrement théorique, passons un petit peu à la pratique par la manipulation des outils en ligne de commande.
|
|
|
2 DBus par la ligne de commande
|
Dans ce paragraphe, avant de nous attaquer au développement de DBus dans divers langages, nous allons voir comment manipuler les outils à notre disposition sur notre système préféré. Pour la suite de ce tutoriel, il va falloir installer divers paquets (dont la compilation n’est pas du tout le thème de cet article). Voici la liste de ces paquets (pour Debian) :
dbus : paquet principal installant le démon DBus.
dbus-x11 : paquet contenant des utilitaires en ligne de commande
Une fois ces paquets installés, vous devriez avoir le démon DBus lancé. Un script est créé à l’endroit habituel des applications serveurs : /etc/init.d/dbus [start|stop|restart|...]. Certains services disposent d’une interface nommée " Introspectable ". Cette dernière permet, en interrogeant ledit service, de récupérer la liste des méthodes, des signaux, des attributs qu’il expose. Voyons ceci sur un exemple.
Une fois le démon dbus lancé, on peut l’interroger. Il dispose bien sûr de l’interface Introspectable et la méthode associée est une méthode qui ne prend pas de paramètre. L’outil dbus-send (disponible dans le paquet dbus-x11) permet à l’utilisateur d’envoyer des messages sur le bus.
|
john@Odyssee$ dbus-send --print-reply --session \
--dest="org.freedesktop.DBus" \
/org/freedesktop/DBus/Introspectable \
org.freedesktop.DBus.Introspectable.Introspect
|
L’outil dbus-send permet d’envoyer des messages sur un bus spécifié en paramètre :
--session : pour le bus session (bus par défaut si rien n’est spécifié) ;
--system : pour le bus système ;
--dest : service distant sur lequel on va vouloir appliquer une action ;
/org/freedesktop/DBus/Introspectable : interface du service en question, sachant qu’un service peut déclarer plusieurs interfaces ;
org.freedesktop.DBus.Introspectable.Introspect : action à exécuter.
La réponse à la commande précédente est une sortie XML décrivant le service, c’est-à -dire la liste de ses interfaces. En voilà une partie du résultat sur ma machine :
|
01: method return sender=org.freedesktop.DBus -> dest=:1.34 reply_serial=2
02: string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object
03: Introspection 1.0//EN"
04: "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
05: <node>
06: <interface name="org.freedesktop.DBus.Introspectable">
07: <method name="Introspect">
08: <arg name="data" direction="out" type="s"/>
09: </method>
10: </interface>
11: #Fin de l’interface "org.freedesktop.DBus.Introspectable"
12: <interface name="org.freedesktop.DBus">
13: <method name="Hello">
14: <arg direction="out" type="s"/>
15: </method>
16: <method name="RequestName">
17: <arg direction="in" type="s"/>
18: <arg direction="in" type="u"/>
19: <arg direction="out" type="u"/>
20: </method>
21: <method name="ReleaseName">
22: <arg direction="in" type="s"/>
23: <arg direction="out" type="u"/>
24: </method>
25: <method name="StartServiceByName">
26: <arg direction="in" type="s"/>
27: <arg direction="in" type="u"/>
28: <arg direction="out" type="u"/>
29: </method>
30: <method name="ListNames">
31: <arg direction="out" type="as"/>
32: </method>
33: <method name="ListActivatableNames">
34: <arg direction="out" type="as"/>
35: </method>
36: ...
37: </interface> #Fin de l’interface org.freedesktop.DBus
38: </node>
|
Dans la liste des méthodes de l’interface org.freedesktop.DBus, on en remarque une qui semble intéressante : ListNames. D’après la description XML <arg direction="out" type="as"/>, cette méthode ne prend pas de paramètre en entrée et renvoie un tableau de chaînes de caractères (as = array of string). Voilà alors son exécution :
|
john@Odyssee$ dbus-send --print-reply --session \
--dest="org.freedesktop.DBus" \
/org/freedesktop/DBus \
org.freedesktop.DBus.ListNames
01: method return sender=org.freedesktop.DBus -> dest=:1.43 reply_serial=2
02: array [
03: string "org.freedesktop.DBus"
04: string ":1.7"
05: string ":1.4"
06: string ":1.0"
07: string ":1.43"
08: string ":1.9"
09: string ":1.5"
10: string "im.pidgin.purple.PurpleService"
11: string ":1.1"
12: string "org.gnome.GnomeVFS.Daemon"
13: string ":1.6"
14: string "org.gnome.SettingsDaemon"
15: ]
|
Cette méthode renvoie la liste de chacun des services actifs sur le bus spécifié.
|
Note
|
|
|
Je ne sais pas réellement à quoi correspondent les services avec ce genre de nom string ":1.7".
|
Concernant le service DBus, je ne peux que conseiller le lecteur de se rendre sur cette page [6], pour avoir une description complète des méthodes ou signaux mis à disposition. Une autre chose bien utile à savoir est que l’on peut inspecter tout type de service. Supposons que pidgin soit lancé : il lance un service DBus sur le bus session et on peut alors connaître la liste de ses méthodes par l’appel suivant :
|
dbus-send --print-reply --session \
--dest="im.pidgin.purple.PurpleService" \
/im/pidgin/purple/PurpleObject \
org.freedesktop.DBus.Introspectable.Introspect
#Sortie
01: method return sender=:1.4 -> dest=:1.52 reply_serial=2
02: string "<!DOCTYPE node PUBLIC ‘-//freedesktop//DTD
03: D-BUS Object Introspection 1.0//EN’
04: ‘http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd’>
05: <node name=’/im/pidgin/purple/PurpleObject’>
06: <interface name=’im.pidgin.purple.PurpleInterface’>
07: <method name=’PurpleAccountsFindAny’>
08: <arg name=’name’ type=’s’ direction=’in’/>
09: <arg name=’protocol’ type=’s’ direction=’in’/>
10: <arg name=’RESULT’ type=’i’ direction=’out’/>
11: </method>
12: <method name=’PurpleAccountsFindConnected’>
13: <arg name=’name’ type=’s’ direction=’in’/>
14: <arg name=’protocol’ type=’s’ direction=’in’/>
15: <arg name=’RESULT’ type=’i’ direction=’out’/>
16: </method>
17: ....
18: </interface>
19: </node>
|
L’utilisation du service pidgin est très bien expliqué ici [7] (en anglais). Il existe d’autre part des outils graphiques pour visualiser les services, interfaces et autres consorts de DBus :
QDBusViewer [8] : application écrite en Qt via le port Qt de DBus ;
dbus-inspector [9] : logiciel qui utilise le port Python de DBus.
Maintenant que DBus est démystifié en partie, on va pouvoir commencer à passer du côté développement de la chose. La progression va se faire de façon totalement incrémentale :
Écriture d’une interface avec une méthode classique : HelloWorld et développement du processus qui va implémenter cette méthode.
Écriture d’un client qui récupère une référence sur l’objet précédemment écrit et qui appelle la procédure distante.
Ajout d’un signal dans l’interface et mise à jour du code " serveur " pour implémenter ce signal. De plus, cet objet enverra un signal à chacun de ses clients abonnés toutes les 5 secondes.
Mise à jour du client pour s’abonner à ce signal et lui ajouter une boucle de lecture de notification.
Passons alors aux choses sérieuses, à savoir le développement.
|
|
|
2.1 Une première interface
|
Originellement, DBus est écrit en C, mais il existe de nombreux ports de DBus pour différents langages tels que : GLib, Python, Perl, Java, C#, Qt (C++) ou alors, pour ceux qui ne veulent pas s’embarquer toute la bibliothèque Qt, il existe un port C++ pur développé pour les besoins du projet OpenWengo [10][11]. Pour les petits exemples qui vont suivre, le serveur et le client seront développés avec le port GLib. Un exemple d’utilisation de l’implémentation DBus en Python sera montré.
Ci-dessous, on trouvera une première description d’interface qui sera expliquée par la suite.
|
01: <?xml version="1.0" encoding="UTF-8" ?>
02: <node name="/org/linuxmag/sample/DummyObject">
03: <interface name="org.linuxmag.sample.DummyInterface">
04: <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="dummy_interface"/>
05: <method name="HelloWorld">
06: <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="hello_world"/>
07: <arg type="s" name="name" direction="in" />
08: <arg type="s" name="hello" direction="out" />
09: </method>
10: </interface>
11: </node>
|
Sur cette description, on déclare un objet distant dont le nom est DummyObject, qui a pour chemin /org/linuxmag/sample/DummyObject. De plus, cet objet expose une seule interface dont le nom est le suivant : org.linuxmag.sample.DummyInterface. Ces trois paramètres, qui seront décrits plus loin, seront la seule façon d’identifier les méthodes et les signaux à travers les API mises à disposition. De plus, comme on peut à juste titre le remarquer, cette interface n’expose qu’une seule méthode appelée HelloWorld. On remarque en étudiant de près la description de cette méthode qu’elle prend en argument une chaîne de caractères et renvoie en sortie une autre chaîne.
Un autre point qui n’a pas encore été abordé dans cet article est la balise <annotation>. Comme il est écrit dans la documentation officielle de DBus, les annotations sont des paires de clés/valeurs portant sur des méthodes, des signaux ou des propriétés. En voilà une petite description :
org.freedesktop.DBus.Deprecated : valeurs = (true|false). Indique si l’entité correspondant est dépréciée ou pas.
org.freedesktop.DBus.GLib.CSymbol : valeurs = (string). Indique au générateur de code le nom réel de la méthode, de l’interface, du signal...
org.freedesktop.DBus.Method.NoReply : valeurs = (true|false). Comme on peut le deviner, cette clé va indiquer si une méthode retourne un résultat ou non.
Une fois le fichier XML écrit, il est de bon ton d’utiliser un outil qui va générer les couches serveur et client (tout comme en CORBA avec IDL ou en RMI dans les technologies Java afin de générer les squelettes client et serveur). Le paquet dbus fournit un tel générateur, ce dernier s’appelle dbus-binding-tool. La partie qui suit va expliquer brièvement son utilisation.
|
|
|
2.2 L’implémentation du serveur
|
Comme annoncé ci-avant, le serveur sera développé avec le binding GLib. On utilise la ligne de commande suivante pour générer une partie du code serveur.
|
dbus-binding-tool --mode=glib-server --prefix=dummy_object DummyObject.xml > DummyObjectGlue.h
|
Rien de bien transcendant n’est généré qui nécessite un quelconque commentaire. On va s’attaquer directement au développement de l’objet Dummy_Object.
|
01: //DummyObject.h
02: #include <glib-object.h>
03:
04: typedef struct DummyObject DummyObject;
05: typedef struct DummyObjectClass DummyObjectClass;
06:
07: GType hello_world_get_type (void);
08:
09: struct DummyObject
10: {
11: GObject parent;
12: };
13:
14: struct DummyObjectClass
15: {
16: GObjectClass parent;
17: };
18:
19: #define DUMMYOBJ_TYPE (dummy_object_get_type ())
20: #define DUMMYOBJ_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
21: HELLOWORLD_TYPE, DummyObjectClass))
21:
22: /**
23: * Prend en parametre une chaine de caracteres et
24: * renvoie : "Hello " + chaine + " ! "
25: */
26: gboolean hello_world(DummyObject *dummy,
27: const char* string,
28: char **out_string,
29: GError **error);
|
Sur cette petite portion de code, il n’y a pas grand-chose à dire si ce n’est que l’on définit la structure d’objet et de classe au niveau de l’API " Glib " pour l’object que l’on développe. Pour ce qui est de la signature des méthodes de l’interface :
son type de retour doit être de type booléen ;
son premier paramètre est un pointeur sur l’instance de l’objet ;
s’ensuit la liste des paramètres d’entrée de la méthode ;
s’ensuit la liste des paramètres de sortie de la méthode ;
le dernier paramètre de la signature doit être un champ de type GError **. Si la fonction retourne FALSE, alors l’erreur est contenue dedans.
Ci-dessous se trouve le code pour l’implémentation du serveur à proprement parler :
|
01: //Serv.c
02: //Includes globaux systèmes
03: #include <stdlib.h>
04: #include <glib.h>
05: #include <dbus/dbus-glib.h>
06: #include <dbus/dbus-glib-bindings.h>
07:
08: //Includes propres aux objects développés
09: #include "DummyObject.h"
10: #include "DummyObjectGlue.h"
11:
12: /* Génération d’un ensemble de déclarations
13: * de fonctions propre aux objets de type : DummyObject
14: */
15: G_DEFINE_TYPE(DummyObject, dummy_object, G_TYPE_OBJECT)
16:
17:
18: /* Fonction appelée lors de l’initialisation
19: * de la classe
20: */
21: static void dummy_object_class_init (DummyObjectClass *dummy_class)
22: {
23: }
24:
25: /* Fonction appelée lors de l’instanciation
26: * d’un object de type DummyObject.
27: */
28: static void dummy_object_init (DummyObject *dummy)
29: {
30: /* - Installation du mécanisme d’introspection,
31: * permettant de faire l’appel de méthodes via un nom.
32: * - Le second paramètre de cette fonction : &dbus_glib_dummy_object_object_info
33: * est généré lors de l’appel à dbus-binding-tool
34: * et sa définition peut être retrouvée dans le fichier
35: * DummyObjectGlue.h
36: */
37: dbus_g_object_type_install_info (DUMMYOBJ_TYPE,
38: &dbus_glib_dummy_object_object_info);
39: }
40:
41: gboolean hello_world(DummyObject *obj,
42: const char* in_string,
43: char **out_string,
44: GError **error)
45: {
46: *out_string = g_strconcat("Hello",
47: " ",
48: in_string,
49: NULL);
50:
51: return TRUE;
52: }
53:
54: /* Fonction qui écrit le message d’erreur et qui stoppe
55: * l’exécution du programme
56: */
57: void die (const char *prefix, GError *error)
58: {
59: g_error("%s: %s", prefix, error->message);
60: g_error_free (error);
61: exit(1);
62: }
63:
64: #define DUMMYOBJECT_PATH "/org/linuxmag/sample/DummyObject"
65: #define DUMMYOBJECT_SERVICE_NAME "org.linuxmag.sample.DummyObject"
|
|
71: /* pointeur sur une connection DBus */
72: DBusGConnection *connection;
73: GError *error = NULL;
74: GObject *obj;
75: DBusGProxy *driver_proxy;
76: guint32 request_name_ret;
77:
78: g_type_init ();
79: loop = g_main_loop_new (NULL, FALSE);
80:
81: g_print ("Lancement DummyObject\n");
82:
83: /* Récupération d’une connection
84: * sur le bus système
85: */
86: connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
87: if (connection == NULL)
88: die ("Failed to open connection to bus", error);
89: else
90: g_print ("Connection réussie sur le BUS SYSTEME\n");
91:
92: obj = g_object_new (DUMMYOBJ_TYPE, NULL);
93: /* Note : l’appel à cette fonction va appeler les
94 * fonctions définies plus haut :
95: * - dummy_object_class_init
96: * - dummy_object_init
97: */
98:
99:
100: dbus_g_connection_register_g_object (connection,
101: DUMMYOBJECT_PATH,
102: obj);
103: /* Une fois cet appel effectué :
104: * - les propriétés et les signaux sont
105: * accessibles ` distance.
106: * - les méthodes ne seront accessibles si, et
107: * seulement si le développeur n’a pas oublié l’appel
108: * ` l’installation de l’introspection. (cf un peu plus haut
109: * dbus_g_object_type_install_info)
110: */
111: /* Récupération d’un proxy sur le bus */
112: driver_proxy = dbus_g_proxy_new_for_name (connection,
113: DBUS_SERVICE_DBUS,
114: DBUS_PATH_DBUS,
115: DBUS_INTERFACE_DBUS);
116:
117: /* On essaie de s’enregistrer sur le bus */
118: if (!org_freedesktop_DBus_request_name (driver_proxy,
119: DUMMYOBJECT_SERVICE_NAME,
120: 0, &request_name_ret, &error))
121: die ("Failed to get name", error);
122:
123: if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
124: {
125: g_error ("Got result code %u from requesting name", request_name_ret);
126: exit (1);
127: }
128:
129: g_print ("GLib test service has name ‘%s’\n", DUMMYOBJECT_SERVICE_NAME);
130:
131: g_print ("GLib test service entering main loop\n");
132:
133: /* Si tout s’est bien déroulé, on attend les connections */
134:
135: g_main_loop_run (loop);
|
Voilà pour ce qui est de l’implémentation côté serveur. Rien de bien méchant. On va de suite se lancer côté client. Comme annoncé, deux développements seront faits pour le client :
un avec l’API fournie par GLib-DBus ;
un avec l’API Python-DBus, qui, comme on le remarquera, est beaucoup plus courte et simple.
|
|
|
2.3 L’implémentation du client
|
De quoi a besoin un client pour utiliser un service DBUSÂ ?
du chemin de l’objet : /org/linuxmag/sample/DummyObject ;
du nom du service : org.linuxmag.sample.DummyObject ;
du nom de l’interface : org.linuxmag.sample.DummyInterface.
Dans cette étude de cas, on va développer le client comme si l’on ne disposait pas du fichier XML d’introspection. On verra tout à la fin que faire lorsque l’on dispose de ce fichier.
Le client s’écrit en quelques lignes, et si le serveur était très simple, le client l’est encore plus.
|
01: //Client.c
02: #include <stdlib.h>
03: #include <glib.h>
04: #include <dbus/dbus-glib-bindings.h>
05:
06: /* Pareil que pour le serveur */
07: static void die (const char *prefix, GError *error)
08: {
09: g_error("%s: %s", prefix, error->message);
10: g_error_free (error);
11: exit(1);
12: }
13:
14: static GMainLoop *loop = NULL;
15:
16: #define DUMMYOBJECT_PATH "/org/linuxmag/sample/DummyObject"
17: #define DUMMYOBJECT_SERVICE_NAME "org.linuxmag.sample.DummyObject"
18: #define DUMMYOBJECT_INTERFACE_NAME "org.linuxmag.sample.DummyInterface"
19:
20: int main(int argc, char **argv)
21: {
22: GError *error = NULL;
23: DBusGConnection *connection;
24: DBusGProxy *proxy;
25: char *s_out = NULL;
26: int ret_signal;
27:
28: g_type_init ();
29:
30: loop = g_main_loop_new (NULL, FALSE);
31:
32: /* Récupération d’une connection sur le bus session,
33: * sur lequel on sait que le service tourne
34: */
35: connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
36: if (connection == NULL)
37: die ("Failed to open connection to bus", error);
38:
39: /* Création d’un objet proxy pour interroger un objet distant */
39: proxy = dbus_g_proxy_new_for_name (connection,
40: DUMMYOBJECT_SERVICE_NAME,
41: DUMMYOBJECT_PATH,
42: DUMMYOBJECT_INTERFACE_NAME,
43: );
44: /* Si le proxy est nul, on arrête tout
45: if (proxy == NULL)
46: die ("Failed to create proxy for name owner", error);
47:
48: /* Fonction qui appelle la méthode "HelloWorld" de façon synchrone.
49: * - Ensuite vient la liste de tous les arguments d’entrée
50: * selon ce schéma : (TYPE, valeur).
52: * Cette liste se termine par la constante : G_TYPE_INVALID
53: * - Ensuite la liste des arguments de sortie qui se termine par
54: * une seconde constante : G_TYPE_INVALID
55: */
56: if (!dbus_g_proxy_call (proxy, "HelloWorld", &error,
57: G_TYPE_STRING, "JOHN", G_TYPE_INVALID,
58: G_TYPE_STRING, &s_out, G_TYPE_INVALID))
59: die ("Call to dbus_g_proxy_call HelloWorld failed", error);
60:
61: g_print("Retour = ‘%s’\n", s_out);
62 g_free (s_out);
63: return 0;
64: }
|
Outre les commentaires, il n’y a pas grand-chose de plus à ajouter à ce code source. Comme évoqué un peu plus haut concernant l’API GLib de DBus, on pourrait coder ce client autrement, en disposant du fichier XML d’introspection que l’on a exposé au début. En effet, un appel à l’outil dbus-binding-tool permet de générer quelques lignes de code :
|
dbus-binding-tool --mode=glib-client DummyObject.xml > DummyObjectBinding.h
|
La lecture de ce fichier d’en-tête est laissée au lecteur, mais les trois fonctions générées ne font qu’encapsuler les différents appels à dbus_g_proxy_call et la partie modifiée du code client devient :
|
01: #include "DummyObjectBinding.h"
02: if (!org_linuxmag_sample_DummyInterface_hello_world (proxy, "John", &s_out, &error))
03: die ("Call to HelloWorld failed", error);
|
Comme on va le voir dans la prochaine section, la version Python est encore plus simple. Je trouve personnellement l’API Python vraiment excellente.
Je laisse le soin au lecteur de vérifier auprès de ses outils favoris (apt, aptitude, rpm,emerge,...) que l’API DBus-Python est correctement installée.
|
01: #-- DummyClient.py -- Ne pas le nommer dbus.py comme
02: # j’ai pu en faire l’erreur ...
03 #!/usr/bin/python
04: import dbus
05:
06: bus = dbus.SessionBus()
07:
08: remote_object = bus.get_object(‘org.linuxmag.sample.DummyObject’,
09: ‘/org/linuxmag/sample/DummyObject’);
10: interface = dbus.Interface(remote_object,
11: dbus_interface=’org.linuxmag.sample.DummyInterface’)
12:
13: hello = interface.HelloWorld("John")
14:
15: print hello
|
Qu’en dites-vous ? En moins de cinq lignes de code effectives, on réalise un client pour n’importe quel objet DBus. J’adore ;-).
|
|
|
2.4 Modification de l’interface DummyObject avec un signal
|
Dans cette section, on va se focaliser sur le développement d’un signal émis par le serveur à chacun de ses clients abonnés. Dans le fichier XML de description du service, ces quelques lignes sont à ajouter :
|
01: <signal name="Ping">
02: <arg type="i" name="alive" direction="out"/>
03: </signal>
|
Tout comme précédemment, on va regénérer le fichier DummyObjectGlue.h avec l’outil dbus-binding-tool (même ligne de commande que ci-dessus). Et le serveur se modifie de la sorte :
|
01: //Serv.c
02: static guint signalId;
03: /* Lors de l’initialisation de la classe d’objet de type
04: * DummyObjectClass, on crée un nouveau signal.
05: * [12]
06: */
07: static
08: void dummy_object_class_init(DummyObjectClass *dummy_class)
09: {
10: signalId = g_signal_new("ping",
11: G_OBJECT_CLASS_TYPE(dummy_class),
12: G_SIGNAL_RUN_LAST,
13: 0,
14: NULL,
15: NULL,
16: g_cclosure_marshal_VOID__INT,
17: G_TYPE_NONE,
18: 1,
19: G_TYPE_INT);
20:
21: g_printf("dummy_object_class_init : signalId = %d\n", signalId);
22: }
23: ...
24: /* Fonction appelée périodiquement qui fait
25: * l’émission du signal à proprement parler
26: */
27: gboolean send_ping(GObject *obj)
28: {
29: g_printf("Sent hearbeat !\n");
30: g_signal_emit(obj, signalId, 0, 1);
31: return TRUE;
32: }
33: ...
34: int main(int argc, char **argv) {
35: ...
36: /* Envoi du signal toutes les secondes */
37: g_timeout_add (1000, (GSourceFunc)send_ping, obj);
38: ...
39: }
|
Et côté client, le code devient :
|
01: int main(int argc, char **argv) {
02: ...
03: //attach to a signal
04: dbus_g_proxy_add_signal(proxy,
05: /* Nom du signal */
06: "Ping",
07: /* Un argument de type entier */
08: G_TYPE_INT,
09: /* Fin de la liste des arguments */
10: G_TYPE_INVALID);
11:
12: dbus_g_proxy_connect_signal(proxy,
13: /* Nom du signal */
14: "Ping",
15: /* Nom de la fonction de rappel
16: * à la réception
17: * d’un signal
18: */
19: G_CALLBACK(pingReceptionHandler),
20: NULL,
21: NULL);
22:
23: g_main_loop_run(loop);
24: return 0;
25: }
26:
27: /* fonction de rappel */
28: static void pingReceptionHandler(DBusGProxy* proxy, int ret)
29: {
30: g_printf("pingReceptionHandler called with datas : \n");
31: g_printf("%d\n", ret);
32: }
|
Toutes les secondes, le serveur envoie donc un petit message à chacun de ses observateurs. Ce cas d’utilisation est des plus simples, mais on pourrait très bien imaginer un cas où l’application distante notifie chacun de ses abonnés sur la modification d’une de ses valeurs internes, de la réception d’un email, de la connexion d’un ami dans Pidgin...
Le code client en Python est tout aussi simple que le premier. Je laisse donc soin au lecteur de le regarder de plus près dans l’archive téléchargeable ici [13].
|
|
|
3 Un peu de configuration du démon DBus
|
Dans la partie précédente, nous avons pu voir le développement d’un service et d’une application cliente. Maintenant, je souhaiterais présenter un peu la configuration du démon. En effet, supposons que le service ne soit pas lancé en début de session ou au démarrage de notre système préféré, on aimerait disposer d’un mécanisme de lancement automatique du service au premier appel de ce dernier. C’est dans cette partie que je vais tenter d’apporter quelques réponses.
|
|
|
3.1 Démarrage automatique d’un service au premier appel d’un client
|
La configuration du démon DBus se fait dans le répertoire : /usr/share/dbus-1/services dans lequel on trouve une liste de fichiers ayant pour extension .service. Ces fichiers sont un peu comme les fichiers bien connus .ini comportant une liste de paires (clés/valeurs). Par exemple :
|
01: #Fichier : dhcdbd.service
02: [D-BUS Service]
03: #Nom du service
04: Name=com.redhat.dhcp
05: Exec=/usr/sbin/dhcdbd
|
Rien de bien terriblement complexe. Quand un processus va faire une requête auprès du démon avec un nom de service, le démon va vérifier si ce nom est bien enregistré, auquel cas il lance le binaire associé défini dans ce fichier de configuration. Écrivons notre fichier de service pour notre petit serveur codé au préalable :
|
01: #Fichier org.linuxmag.sample.DummyObject.service
02: [D-BUS Service]
03: Name=org.linuxmag.sample.DummyObject
04: Exec=votreChemin/DummyObject/main_serv
|
Avant de recopier ce fichier dans le répertoire /usr/share/dbus-1/services/, faisons un tout petit test en lançant le client, et observons le message d’erreur :
|
john@Odyssee$ ./main_client
** ERROR **: Call to dbus_g_proxy_call HelloWorld failed:
The name org.linuxmag.sample.DummyObject was not provided by any
.service files
aborting...
Aborted
|
Ok, copions alors notre fichier dans le répertoire. Et on teste à nouveau :
|
john@Odyssee$ ./main_client
Got ‘Hello JOHN’
|
Voilà pour ce qui est du lancement de services à la demande. Le problème, c’est que cette opération nécessite les droits root.
Nous arrivons enfin au terme de cet article sur une technologie que j’affectionne particulièrement de par son efficacité et sa simplicité de mise en œuvre. Il est encore possible d’affiner la configuration du démon DBus en configurant des politiques de sécurité pour que certains groupes, utilisateurs puissent accéder aux services, avec une configuration par service. De plus, j’aurais aimé montrer la simplicité d’utilisation du binding C++, mais je l’ai fait avec Python, qui, je l’avoue, m’a réellement bluffé. J’espère vous avoir ouvert la voie vers de plus amples recherches sur ce sujet que je trouve passionnant. À bientôt !
Ok ok apparemment les lignes manquantes du milieu seraient :
+ int main(int argc, char *argv[])
+ {
+ GMainLoop *loop;
Quand a la fin, assez logiquement :
+ return 0;
+ }
Merci pour ce tuto qui aide bien pour se lancer dans dbus !
Bonjour,
Merci pour cet article trés intéressant. Concernant la note suite au listage de services actifs sur le bus spécifié. Je penses que des éléments de réponse vous trouverez plus d’informations sur les liens suivants:
http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-names
http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus