D-Bus Tutorial

  • by

1.0 D-Bus

D-Bus is a mechanism for interprocess communication under Linux and other Unix-like systems. D-Bus has a layered architecture. At the lowest level is the D-Bus wire protocol described in the D-Bus Specification. The libdbus library is an implementation of the wire protocol. It provides the C Language interface for communication between two processes. At the highest level is the D-Bus daemon, or the message bus daemon. Processes can connect to a D-Bus daemon and exchange messages. There may be multiple D-Bus daemons running in the system at a time. First, there is the system D-BUS daemon for communication between the kernel, system-wide services and the user. Then, there is the session D-BUS daemon, primarily intended for communication between processes for the desktop applications of the logged-in user.

libdbus is a low level API from the freedesktop.org project. There are other higher level implementations, viz., GDBus for GIO, QtDBus for Qt, dbus-java and sd-bus, for the systemd software suite.

2.0 D-Bus concepts

Message

A message is the unit of data transfer between processes. Messages are discreet as opposed to continuous stream of data transferred between processes by mechanisms like pipes. A message has a header, which identifies its sender, receiver and method or signal name, etc. and message body containing a data payload.

Message Types

There are four types of messages, SIGNAL, METHOD_CALL, METHOD_RETURN and ERROR.

A SIGNAL is a message that is broadcast by a process and can be received by other interested processes.

A METHOD_CALL message is a request by the sender for a particular operation on an object of the receiver. For example, the receiver may be a service with a singleton object. The sender could be a client, requesting the execution of a method by the server. The method call message has the name of the method to be executed and also the arguments required for execution. The receiver is required to execute the method and respond back to the sender with a METHOD_RETURN message containing the result(s) of the operation. Or, if there was an error, the receiver can respond with an ERROR message.

Message bus

A message bus is a daemon process which routes messages between other processes.

Service

A service is a daemon process that provides some utility in the system. A service is a server process which does work for the clients. A service has a singleton object.

Object

An object is an entity in a process, which does some work. An object is identified by a path name. A path is like a complete file name in the system. So, an object might have a path name like, /com/example/someserver. An object has members, which means methods and signals.

Interfaces

An interface is a group of functions. An object supports one or more interfaces. The interfaces supported by an object specify the members of that object.

Connection names

When an application connects with the D-Bus daemon, it is assigned a unique connection name. A unique connection name starts with the colon character ":".

An application may also ask for a well-known name to be assigned to a connection. This is of the form of a reverse domain name like, com.example.somename.

For each connection name there is an application which is its primary owner. All messages sent to a name are delivered to the primary owner. Then, there is a queue of secondary owners. As soon as a primary owner relinquishes the name, the application at the top of the queue of aspiring secondary owners takes charge and becomes the new primary owner.

3.0 D-Bus Configuration

The D-Bus daemon configuration files are located in the /usr/share/dbus-1 directory. The configuration files, system.conf and session.conf for the system bus and session bus respectively are also symbolically linked in the /etc/dbus-1 directory. There are directives in system.conf and session.conf to include files in the system.d and session.d sub-directories, respectively.

4.0 Use Cases

D-Bus is used for interprocess communication between a server or service process and clients. There are two situations. First, there, is only one-way communication. The clients may inform the server of some events and the server makes a note of it. The server does not respond back to the client. Similarly, a service may broadcast some information and those (processes) which are interested, make a note of it. The clients do not respond back to the server. The second case is a full-fledged two-way communication. A client sends some information in a request message to the server. The server receives the message, processes the data and sends a reply message back to the client. The first case is accomplished using signals whereas the second is implemented using method calls. The example given below pertains to the second case.

5.0 A client-server example

The server in this example provides a simple addition service. The clients send two integers in a method call. The server adds them up and sends the answer as the reply. We will use the low-level libdbus interface from C programs.

The server program is,

/*
 *
 *     add-server.c: server program, receives message,
 *                   adds numbers in message and 
 *                   gives back result to client
 *
 */

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

const char *const INTERFACE_NAME = "in.softprayog.dbus_example";
const char *const SERVER_BUS_NAME = "in.softprayog.add_server";
const char *const OBJECT_PATH_NAME = "/in/softprayog/adder";
const char *const METHOD_NAME = "add_numbers";

DBusError dbus_error;
void print_dbus_error (char *str);
bool isinteger (char *ptr);

int main (int argc, char **argv)
{
    DBusConnection *conn;
    int ret;

    dbus_error_init (&dbus_error);

    conn = dbus_bus_get (DBUS_BUS_SESSION, &dbus_error);

    if (dbus_error_is_set (&dbus_error))
        print_dbus_error ("dbus_bus_get");

    if (!conn) 
        exit (1);

    // Get a well known name
    ret = dbus_bus_request_name (conn, SERVER_BUS_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error);

    if (dbus_error_is_set (&dbus_error))
        print_dbus_error ("dbus_bus_get");

    if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
        fprintf (stderr, "Dbus: not primary owner, ret = %d\n", ret);
        exit (1);
    }

    // Handle request from clients
    while (1) {
        // Block for msg from client
        if (!dbus_connection_read_write_dispatch (conn, -1)) {
            fprintf (stderr, "Not connected now.\n");
            exit (1);
        }
     
        DBusMessage *message;

        if ((message = dbus_connection_pop_message (conn)) == NULL) {
            fprintf (stderr, "Did not get message\n");
            continue;
        } 
        
        if (dbus_message_is_method_call (message, INTERFACE_NAME, METHOD_NAME)) {
            char *s;
            char *str1 = NULL, *str2 = NULL;
            const char space [4] = " \n\t";
            long i, j;
            bool error = false;

            if (dbus_message_get_args (message, &dbus_error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
                printf ("%s", s);
                // Validate received message
                str1 = strtok (s, space);
                if (str1)
                    str2 = strtok (NULL, space);
 
                if (!str1 || !str2)
                    error = true; 
            
                if (!error) {
                    if (isinteger (str1))
                        i = atol (str1);
                    else
                        error = true;
                }
                if (!error) {
                    if (isinteger (str2))
                        j = atol (str2);
                    else
                        error = true;
                }

                if (!error) {
                    // send reply
                    DBusMessage *reply;
                    char answer [40];

                    sprintf (answer, "Sum is %ld", i + j);
                    if ((reply = dbus_message_new_method_return (message)) == NULL) {
                        fprintf (stderr, "Error in dbus_message_new_method_return\n");
                        exit (1);
                    }
    
                    DBusMessageIter iter;
                    dbus_message_iter_init_append (reply, &iter);
                    char *ptr = answer;
                    if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &ptr)) {
                        fprintf (stderr, "Error in dbus_message_iter_append_basic\n");
                        exit (1);
                    }

                    if (!dbus_connection_send (conn, reply, NULL)) {
                        fprintf (stderr, "Error in dbus_connection_send\n");
                        exit (1);
                    }

                    dbus_connection_flush (conn);
                
                    dbus_message_unref (reply);	
                }
                else // There was an error
                {
                    DBusMessage *dbus_error_msg;
                    char error_msg [] = "Error in input";
                    if ((dbus_error_msg = dbus_message_new_error (message, DBUS_ERROR_FAILED, error_msg)) == NULL) {
                         fprintf (stderr, "Error in dbus_message_new_error\n");
                         exit (1);
                    }

                    if (!dbus_connection_send (conn, dbus_error_msg, NULL)) {
                        fprintf (stderr, "Error in dbus_connection_send\n");
                        exit (1);
                    }

                    dbus_connection_flush (conn);
                
                    dbus_message_unref (dbus_error_msg);	
                }
            }
            else
            {
                print_dbus_error ("Error getting message");
            }
        }
    }

    return 0;
}


bool isinteger (char *ptr)
{

    if (*ptr == '+' || *ptr == '-')
        ptr++;

    while (*ptr) {
        if (!isdigit ((int) *ptr++))
            return false;
    }
    
    return true;
}

void print_dbus_error (char *str) 
{
    fprintf (stderr, "%s: %s\n", str, dbus_error.message);
    dbus_error_free (&dbus_error);
}

And, the client program is

/*
 *
 *     add-client.c: client program, takes two numbers as input,
 *                   sends to server for addition,
 *                   gets result from server,
 *                   prints the result on the screen
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
#include <string.h>
#include <stdbool.h>
#include <ctype.h>

#include <dbus/dbus.h>


const char *const INTERFACE_NAME = "in.softprayog.dbus_example";
const char *const SERVER_BUS_NAME = "in.softprayog.add_server";
const char *const CLIENT_BUS_NAME = "in.softprayog.add_client";
const char *const SERVER_OBJECT_PATH_NAME = "/in/softprayog/adder";
const char *const CLIENT_OBJECT_PATH_NAME = "/in/softprayog/add_client";
const char *const METHOD_NAME = "add_numbers";

DBusError dbus_error;
void print_dbus_error (char *str);

int main (int argc, char **argv)
{
    DBusConnection *conn;
    int ret;
    char input [80];

    dbus_error_init (&dbus_error);

    conn = dbus_bus_get (DBUS_BUS_SESSION, &dbus_error);

    if (dbus_error_is_set (&dbus_error))
        print_dbus_error ("dbus_bus_get");

    if (!conn) 
        exit (1);

    printf ("Please type two numbers: ");
    while (fgets (input, 78, stdin) != NULL) {

        // Get a well known name
        while (1) {
            ret = dbus_bus_request_name (conn, CLIENT_BUS_NAME, 0, &dbus_error);

            if (ret == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) 
               break;

            if (ret == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
               fprintf (stderr, "Waiting for the bus ... \n");
               sleep (1);
               continue;
            }
            if (dbus_error_is_set (&dbus_error))
               print_dbus_error ("dbus_bus_get");
        }
        
        DBusMessage *request;

        if ((request = dbus_message_new_method_call (SERVER_BUS_NAME, SERVER_OBJECT_PATH_NAME, 
                           INTERFACE_NAME, METHOD_NAME)) == NULL) {
            fprintf (stderr, "Error in dbus_message_new_method_call\n");
            exit (1);
        }

        DBusMessageIter iter;
        dbus_message_iter_init_append (request, &iter);
        char *ptr = input;
        if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &ptr)) {
            fprintf (stderr, "Error in dbus_message_iter_append_basic\n");
            exit (1);
        }
        DBusPendingCall *pending_return;
        if (!dbus_connection_send_with_reply (conn, request, &pending_return, -1)) {
            fprintf (stderr, "Error in dbus_connection_send_with_reply\n");
            exit (1);
        }

        if (pending_return == NULL) {
            fprintf (stderr, "pending return is NULL");
            exit (1);
        }

        dbus_connection_flush (conn);
                
        dbus_message_unref (request);	

        dbus_pending_call_block (pending_return);

        DBusMessage *reply;
        if ((reply = dbus_pending_call_steal_reply (pending_return)) == NULL) {
            fprintf (stderr, "Error in dbus_pending_call_steal_reply");
            exit (1);
        }

        dbus_pending_call_unref	(pending_return);

        char *s;
        if (dbus_message_get_args (reply, &dbus_error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
            printf ("%s\n", s);
        }
        else
        {
             fprintf (stderr, "Did not get arguments in reply\n");
             exit (1);
        }
        dbus_message_unref (reply);	

        if (dbus_bus_release_name (conn, CLIENT_BUS_NAME, &dbus_error) == -1) {
             fprintf (stderr, "Error in dbus_bus_release_name\n");
             exit (1);
        }

        printf ("Please type two numbers: ");
    }

    return 0;
}

void print_dbus_error (char *str) 
{
    fprintf (stderr, "%s: %s\n", str, dbus_error.message);
    dbus_error_free (&dbus_error);
}

The makefile for building the software is given below. The spaces to the left of gcc and rm commands are for the tab character.

#
# Makefile
#

all: add-server add-client

%.o: %.c
        gcc -Wall -c $< `pkg-config --cflags dbus-1`

add-server: add-server.o
        gcc add-server.o -o add-server `pkg-config --libs dbus-1`

add-client: add-client.o
        gcc add-client.o -o add-client `pkg-config --libs dbus-1`

.PHONY: clean
clean:
        rm *.o add-server add-client

We can compile and run the server and client programs,

Server and client processes using D-Bus

6.0 REFERENCES

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

You may like these, also

  • POSIX message queues in LinuxPOSIX message queues in Linux 1.0 POSIX Message queues POSIX interprocess comunication (IPC) was introduced in the POSIX.1b standard (IEEE Std 1003.1b-1993) for real time…
  • POSIX Shared Memory in LinuxPOSIX Shared Memory in Linux 1.0 Shared Memory Shared memory is the fastest method of interprocess communication (IPC) under Linux and other Unix-like systems. The…
  • POSIX Semaphores in LinuxPOSIX Semaphores in Linux 1.0 Semaphores Semaphores are used for process and thread synchronization. Semaphores are clubbed with message queues and shared memory under…
  • fork and exec system calls in Linuxfork and exec system calls in Linux 1.0 fork and exec system calls Suppose we wish to write a "shell program" which would execute another program. Now,…
  • Connecting two computers with Ethernet LAN cableConnecting two computers with Ethernet LAN cable Quite often, we wish to connect two computers back to back using an Ethernet LAN cable. It may be because…
  • D-Bus TutorialD-Bus Tutorial 1.0 D-Bus D-Bus is a mechanism for interprocess communication under Linux and other Unix-like systems. D-Bus has a layered architecture.…
  • Socket programming using the select system callSocket programming using the select system call 1.0 Client-Server Paradigm The Client-Server paradigm divides the software architecture of a system in two parts, the server and its…
  • System V message queues in LinuxSystem V message queues in Linux 1.0 Message queues Message queues are one of the interprocess communication mechanisms available under Linux. Message queues, shared memory and…
  • POSIX Threads Synchronization in CPOSIX Threads Synchronization in C 1.0 POSIX Threads Synchronization POSIX Threads provide multiple flows of execution within a process. The threads have their own stacks…
  • System V Shared Memory in LinuxSystem V Shared Memory in Linux 1.0 Shared Memory Shared memory is one of the three interprocess communication (IPC) mechanisms available under Linux and other Unix-like…