D-Bus - El sistema de comunicación de procesos para desktop

D-Bus es un sistema de comunicación entre procesos (IPC: Interprocess communication) desarrollado por freedesktop.org, que permite que las aplicaciones de escritorio puedan comunicarse entre sí o con procesos del sistema. GNOME es uno de los entornos de escritorio que implementan este sistema.

D-Bus permite a las aplicaciones hablar entre ellas siguiendo un patrón establecido. Este usa un sistema de bus en el que una de las partes publica un comando o mensaje y las otras partes pueden escuchar en el bus y ejecutar acciones sobre los comandos solicitados.

Existen tres canales separados: system bus, session bus y private bus:

  • El bus de sistema (system bus) esta dedicado a los mensajes de todo el sistema, como la creación de un usuario o notificaciones de hardware
  • El bus de sesión (session bus) corre para el usuario del escritorio en ejecución. Todas las aplicaciones del usuario que corren en el escritorio pueden escuchar y reaccionar al bus
  • El bus privado (private bus) es un bus punto a punto y solo las partes conectadas pueden hablar entre ellas

Cualquier aplicación puede establecer una conexión con el bus. Cada conexión tienen un nombre que parece un nombre de dominio invertido como: org.freedesktop.NetworkManager. Las aplicaciones pueden publicar servicios en el bus con un identificador llamado path. El path tiene un formato similar al path del sistema de ficheros como: /org/freedesktop/NetworkManager/StateChanged.

El sistema D-Bus funciona por un lado como servidor, donde las aplicaciones pueden publicar servicios de acuerdo a un interface común. Este funciona como un API el cual tiene que ser implementado correctamente siguiendo las especificaciones tanto por la parte cliente como por la parte servidor.

La aplicación que actúa de servidor en el bus puede también emitir un mensaje con respecto a algún evento, que permite que los clientes se suscriban para recibir ese mensaje y lanzar a su vez algún evento al recibir la señal.

Por otro lado los clientes pueden enviar un mensaje con un comando al bus, especificando el nombre de la conexión y el path. La aplicación que provee el servicio reacciona a la petición, y ejecuta una acción pudiendo enviar algún mensaje de vuelta al cliente.

La siguiente tabla muestra los tipos utilizados en D-Bus según las especificaciones versión 0.36 (21 de abril de 2020):

Categoría Nombre Código Descripción
reservado INVALID 0 (ASCII NUL) No es un código de tipo válido, utilizado para terminar las firmas
fijo, básico BYTE 121 (ASCII 'y') Entero sin signo de 8 bits
fijo, básico BOOLEAN 98 (ASCII 'b') Valor booleano, 0 es FALSE y 1 es TRUE. Todo lo demás es inválido
fijo, básico INT16 110 (ASCII 'n') Entero con signo de 16 bits
fijo, básico UINT16 113 (ASCII 'q') Entero sin signo de 16 bits
fijo, básico INT32 105 (ASCII 'i') Entero con signo de 32 bits
fijo, básico UINT32 117 (ASCII 'u') Entero sin signo de 32 bits
fijo, básico INT64 120 (ASCII 'x') Entero con signo de 64 bits
fijo, básico UINT64 116 (ASCII 't') Entero sin signo de 64 bits
fijo, básico DOUBLE 100 (ASCII 'd') IEEE 754 doble
como string, básico STRING 115 (ASCII 's') Cadena UTF-8 (debe ser válida UTF-8). Debe tener una terminación nula y no contener otros bytes nulos.
como string, básico OBJECT_PATH 111 (ASCII 'o') Nombre de una instancia de objeto
como string, básico SIGNATURE 103 (ASCII 'g') Un tipo firma
contenedor ARRAY 97 (ASCII 'a') Array
contenedor STRUCT 114 (ASCII 'r'),
40 (ASCII '('),
41 (ASCII ')')
Estructura; El código de tipo 114 'r' está reservado para su uso en enlaces e implementaciones para representar el concepto general de una estructura, y no debe aparecer en las firmas utilizadas en D-Bus.
contenedor VARIANT 118 (ASCII 'v') Tipo variant (el tipo del valor es parte del valor en sí)
contenedor DICT_ENTRY 101 (ASCII 'e'),
123 (ASCII '{'),
125 (ASCII '}')
Entrada de diccionario o mapa (matriz de pares clave-valor). El código de tipo 101 'e' está reservado para su uso en enlaces e implementaciones para representar el concepto general de un dict o dict-entry, y no debe aparecer en las firmas utilizadas en D-Bus.
fijo, básico UNIX_FD 104 (ASCII 'h') Descriptor de archivo Unix
reservado (reservado) 109 (ASCII 'm') Reservado para un tipo 'quizás' compatible con el de GVariant , y no debe aparecer en las firmas utilizadas en D-Bus hasta que se especifique aquí
reservado (reservado) 42 (ASCII '*') Reservado para su uso en bindings/implementaciones para representar cualquier tipo completo y no debe aparecer en las firmas utilizadas en D-Bus
reservado (reservado) 63 (ASCII '?') Reservado para su uso en bindings/implementaciones para representar cualquier tipo completo y no debe aparecer en las firmas utilizadas en D-Bus
reservado (reservado) 64 (ASCII '@'),
38 (ASCII '&'),
94 (ASCII '^')
Reservado para uso interno por bindings/implementaciones, y no debe aparecer en las firmas utilizadas en D-Bus. GVariant usa estos códigos de tipo para codificar convenciones de llamadas

Uso de D-Bus

Vamos a ver un programa de ejemplo donde nos conectaremos al bus de sistema, concretamente a org.freedesktop.NetworkManager para acceder al path que nos muestra los cambios en el adaptador de red: /org/freedesktop/NetworkManager/StateChanged.

Este el programa completo chNetmon.c:

 /* 
 * chNetmon.c 
 * 
 *************************************************************** 
 * 
 * (c) Author: David Quiroga 
 * e-mail: david (at) clibre (dot) io 
 * 
gcc -g -Wall `pkg-config --cflags gio-2.0` chNetmon.c -o chNetmon `pkg-config --libs gio-2.0` 
 * 
*/ 
 
#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <locale.h> 
#include <gio/gio.h> 
#include "chNetmon.h" 
 
static void signal_network_state_changed_cb(GDBusConnection *connection, 
        const gchar *sender_name,const gchar *object_path, 
        const gchar *interface_name,const gchar *signal_name, 
        GVariant *parameters, gpointer user_data) 
{ 
    guint32 state; 
    GHashTable* hash_estados = user_data; 
 
    g_variant_get (parameters, "(u)", &state); 
 
    g_print("%s\n", (char *) g_hash_table_lookup(hash_estados,  
                        GINT_TO_POINTER(state))); 
} 
 
static void 
signal_handler (int sig) 
{  
    g_main_loop_quit (loop); 
} 
 
int main(int argc,char *argv[]) 
{ 
    GDBusConnection *conn=NULL; 
    int filter_id=0; 
    GError *err=NULL; 
    GHashTable* hash_estados = g_hash_table_new(g_direct_hash, g_direct_equal); 
 
    setlocale(LC_ALL, ""); 
 
    signal (SIGTERM, signal_handler); 
    signal (SIGINT, signal_handler); 
#ifdef SIGHUP 
    signal (SIGHUP, signal_handler); 
#endif 
 
    if ((conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,&err)) == NULL) { 
        if (err) fprintf(stderr,"g_bus_get error: %s\n",err->message); 
        exit(EXIT_FAILURE); 
    } /* if */ 
 
    loop = g_main_loop_new(NULL,0); 
 
    // Llenamos el hash con los valores de los estados de NetworkManager 
    for(int i=0; i<(sizeof(estados_de_nm) / sizeof(NM_state_s)); i++) 
        g_hash_table_insert(hash_estados, GINT_TO_POINTER(estados_de_nm[i].nmvalor),  
                                            estados_de_nm[i].nmdetalle);  
 
    filter_id = g_dbus_connection_signal_subscribe (conn, 
                            "org.freedesktop.NetworkManager", 
                            "org.freedesktop.NetworkManager", 
                            "StateChanged", 
                            NULL, 
                            NULL, 
                            G_DBUS_SIGNAL_FLAGS_NONE, 
                            signal_network_state_changed_cb, 
                            hash_estados, 
                            NULL); 
 
    g_print("Escuchando DBUS de NetworkManager...\n"); 
    g_main_loop_run (loop); 
    g_print("Saliendo...\n"); 
 
    g_dbus_connection_signal_unsubscribe (conn, filter_id); 
    g_hash_table_destroy(hash_estados); 
    g_main_loop_unref (loop); 
 
    exit(EXIT_SUCCESS); 
}  

y chNetmon.h:

 /* 
 * chNetmon.h 
 * 
 *************************************************************** 
 * 
 * (c) Author: David Quiroga 
 * e-mail: david (at) clibre (dot) io 
 * 
 * 
*/ 
 
/* enum de NetworkManager.h */  
/** 
 * NMState: 
 * @NM_STATE_UNKNOWN: networking state is unknown 
 * @NM_STATE_ASLEEP: networking is not enabled 
 * @NM_STATE_DISCONNECTED: there is no active network connection 
 * @NM_STATE_DISCONNECTING: network connections are being cleaned up 
 * @NM_STATE_CONNECTING: a network connection is being started 
 * @NM_STATE_CONNECTED_LOCAL: there is only local IPv4 and/or IPv6 connectivity 
 * @NM_STATE_CONNECTED_SITE: there is only site-wide IPv4 and/or IPv6 connectivity 
 * @NM_STATE_CONNECTED_GLOBAL: there is global IPv4 and/or IPv6 Internet connectivity 
 * 
 * #NMState values indicate the current overall networking state. 
 **/ 
typedef enum { 
    NM_STATE_UNKNOWN = 0, 
    NM_STATE_ASLEEP = 10, 
    NM_STATE_DISCONNECTED = 20, 
    NM_STATE_DISCONNECTING = 30, 
    NM_STATE_CONNECTING = 40, 
    NM_STATE_CONNECTED_LOCAL = 50, 
    NM_STATE_CONNECTED_SITE = 60, 
    NM_STATE_CONNECTED_GLOBAL = 70 
} NMState; 
 
typedef struct NM_state_s { 
    int nmvalor; 
    char *nmdetalle; 
} NM_state_s; 
 
NM_state_s estados_de_nm[] = { 
    {NM_STATE_UNKNOWN, "NM_STATE_UNKNOWN: el estado de la red es desconocido"}, 
    {NM_STATE_ASLEEP, "NM_STATE_ASLEEP: la red no está habilitada"}, 
    {NM_STATE_DISCONNECTED,  
        "NM_STATE_DISCONNECTED: no hay conexión de red activa"}, 
    {NM_STATE_DISCONNECTING,  
        "NM_STATE_DISCONNECTING: las conexiones de red se están limpiando"}, 
    {NM_STATE_CONNECTING,  
        "NM_STATE_CONNECTING: se está iniciando una conexión de red"}, 
    {NM_STATE_CONNECTED_LOCAL,  
        "NM_STATE_CONNECTED_LOCAL: solo hay conectividad local IPv4 y/o IPv6"}, 
    {NM_STATE_CONNECTED_SITE,  
        "NM_STATE_CONNECTED_SITE: solo hay conectividad IPv4 y/o IPv6 en todo " 
        "el sitio (hasta el router)"}, 
    {NM_STATE_CONNECTED_GLOBAL,  
        "NM_STATE_CONNECTED_GLOBAL: hay conectividad global a Internet IPv4 " 
        "y/o IPv6"} 
}; 
 
GMainLoop *loop; 

Lo primero creamos un manejador de señales básico para finalizar el bucle main y que la aplicación continue la ejecución liberando los recursos y saliendo (por ejemplo con ^C).

 static void 
signal_handler (int sig) 
{  
    g_main_loop_quit (loop); 
} 

Creamos la conexión D-Bus de tipo system bus :

 if ((conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,&err)) == NULL) { 
    if (err) fprintf(stderr,"g_bus_get error: %s\n",err->message); 
    exit(EXIT_FAILURE); 
} /* if */ 

Nos conectamos sincrónicamente al bus de mensajes especificado, en este caso: G_BUS_TYPE_SYSTEM.

Se crea una tabla hash (mapa o diccionario), que es una estructura que usa una clave para identificar un valor de forma exclusiva (una de las funciones de administración de datos que nos proporciona la colección GLib). La rellenamos con todos los valores que tenemos de los estados de NetworkManager que leemos de la estructura NM_state_s estados_de_nm[] que incluimos en chNetmon.h:

 // Llenamos el hash con los valores de los estados de NetworkManager 
for(int i=0; i<(sizeof(estados_de_nm) / sizeof(NM_state_s)); i++) 
    g_hash_table_insert(hash_estados, GINT_TO_POINTER(estados_de_nm[i].nmvalor),  
                                        estados_de_nm[i].nmdetalle);  

Realmente podríamos buscar directamente en la estructura desde la función que maneja la suscripción signal_network_state_changed_cb, pero el objetivo es mostrar el paso de parametros en la función de suscripción que vamos a ver ahora

Nos suscribimos con g_dbus_connection_signal_subscribe donde le pasamos una serie de parámetros, el primero la conexión creada:

 filter_id = g_dbus_connection_signal_subscribe (conn, 
                        "org.freedesktop.NetworkManager", 
                        "org.freedesktop.NetworkManager", 
                        "StateChanged", 
                        NULL, 
                        NULL, 
                        G_DBUS_SIGNAL_FLAGS_NONE, 
                        signal_network_state_changed_cb, 
                        hash_estados, 
                        NULL); 

Los parámetros sender, interface_name y member los podemos obtener usando la aplicación D-Feet, que vimos al hablar de la infraestructura de desarrollo de GNOME:

D-Feet

Nos vamos a suscribir a la señal StateChanged, cuando esta se produzca le indicamos que ejecute la función signal_network_state_changed_cb y le envíe además del parámetro UInt32 de la señal, el hash con los estados que hemos creado antes.

La función que recibe la señal es muy sencilla:

 static void signal_network_state_changed_cb(GDBusConnection *connection, 
        const gchar *sender_name,const gchar *object_path, 
        const gchar *interface_name,const gchar *signal_name, 
        GVariant *parameters, gpointer user_data) 
{ 
    guint32 state; 
    GHashTable* hash_estados = user_data; 
 
    g_variant_get (parameters, "(u)", &state); 
 
    g_print("%s\n", (char *) g_hash_table_lookup(hash_estados,  
                        GINT_TO_POINTER(state))); 
} 

Extrae de los parámetros: el puntero a la tabla hash y el valor enviado por la señal, e imprime el valor con g_print.

Construimos el ejecutable con:

$ gcc -g -Wall `pkg-config --cflags gio-2.0` chNetmon.c -o chNetmon `pkg-config --libs gio-2.0`

Si ejecutamos la aplicación, desconectamos el cable de red y lo volvemos a conectar, veremos como nos va notificando de los cambios producidos:

$ ./chNetmon 
Escuchando DBUS de NetworkManager...
NM_STATE_CONNECTED_LOCAL: solo hay conectividad local IPv4 y/o IPv6
NM_STATE_CONNECTING: se está iniciando una conexión de red
NM_STATE_CONNECTED_LOCAL: solo hay conectividad local IPv4 y/o IPv6
NM_STATE_CONNECTED_SITE: solo hay conectividad IPv4 y/o IPv6 en todo el sitio (hasta el router)
NM_STATE_CONNECTED_GLOBAL: hay conectividad global a Internet IPv4 y/o IPv6

Conclusión

El uso de D-Bus es una parte importante de la integración de las aplicaciones en el escritorio. La facilidad para transmitir datos de una aplicación a otra o la posibilidad de suscribirnos a determinados eventos que puedan ser de utilidad a nuestra aplicación nos permite integrar esta mucho mejor en el escritorio. Por ejemplo, cualquier aplicación que haga uso de de la red podría suscribirse como hemos hecho a NetworkManager de tal forma que estubiera al tanto de la red y de esta forma evitar que falle la conexión, o intentar realizar una cuando el adaptador está caido.

Para un próximo post veremos como hacer un server que emita eventos y un cliente gráfico que reaccione a estos.

Modificado por última vez enViernes, 14 Agosto 2020 19:35
(2 votos)
Etiquetado como :

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.