dbus-glib编程:DBus是什么玩意

时间:2024-03-18 14:59:03

最近做linux桌面环境定制化,发现绝大多数cinnamon、gnome的桌面应用用的是dbus机制。DBus是什么呢?简单说就是同一桌面环境下进程间通讯的一种机制,底层用的是socket,只是封装了一层。那和一般的IPC有啥区别,严格来说没啥区别,都是进程间通讯嘛。只是实现原理和效率不同而已嘛。

正式开始.......

1、Dbus是Linux下进程通信的一种机制,具有分层架构

分3层
Dbus daemon  或者 message bus daemon   守护进程(可能有多个,)
libdbus     是protocol的实现,C接口,提供两个进程间通信的
Dbus protocol协议  (最底层)  https://dbus.freedesktop.org/doc/dbus-specification.html#stability

dbus-glib编程:DBus是什么玩意


守护进程:
system D-BUS daemon(系统守护进程) 用于 kernel 系统级服务 user 之间通讯
session D_BUS daemon(会话守护进程)  主要用于登录用户  的桌面应用之间通讯

dbus守护进程可以有多个,第一个实例object是全局的系统守护进程system daemon,类似于Apache,Sendmail。这个实例具有高安全性,用于全系统间通信。

会话守护进程是当一个用户登录桌面环境后创建的,也就是说多个用户登录时会创建多个会话守护进程;session bus,用于用户程序间相互通信。

 

封装库
libdus 是低层次的库,GDBus for GIO,允许两个应用交换信息;在libdbus库之上还有个message bus daemon守护进程,多个应用连接时,可以从一个app1路由消息到App2

在实际应用中真正用到的封装库是 libdbus-glib(C语言用到),dbus-python。不得不说Python用起来不是一般的清爽,简单好用,C语言要掌握的东西挺多的;


2、dbus概念:

 

Dbus由对象、消息、连接、Dbus后台几部分组成

对象object是一个独立的处理消息的实体,也是进程的实体,以路径名标识(也就是文件路径名);C语言中需要用到gobject相关知识。object可以包含一个或多个接口interface,每个接口又可以包含多个方法method,方法实现了具体的消息处理。

 

interface  一个接口就是一组函数的集合,一个object支持一组或几组接口,对象支持的接口指定对象支持的成员

客户端应用是一个桌面应用程序,是请求消息的发起者。客户端应用通过和自身的相连的一个连接将请求消息发送出去,也通过该连接接收回应的消息、错误消息、系统更新消息等。在一对一的通讯中,请求消息直接到达对象。在多对多的通讯中,请求消息先到达Dbus后台,Dbus后台将消息转发到目的对象。

连接是一个双向的消息传递通道。一个连接将对象和Dbus后台或客户端应用连接起来,连接支持非阻塞式的异步消息发送和阻塞式的同步消息发送。消息通过连接到达目的端后,连接会将挂起在该连接上的进程唤醒,由该进程将消息取走。每个连接都有一个唯一的名字和可选的其他多个名字,用于在多对多通讯时指明消息的发送者和接收者。

连接基于操作系统提供的通讯端口实现消息的交换,现在基于的通讯端口有三种,分别是UNIX的socket、TCP/IP、管道(调试时用)。通讯端口拥有一个地址,服务器在这个地址上监听,客户端则连接到这个地址上。

连接名称   当一个应用连接到dbus daemon,会被赋予唯一名称,以‘:’开始
应用也可以要求分配一个众所周知的名字,反向域名的形式。    
对于每个连接名称,都有一个应用程序,它是其主要所有者。发送给名称的所有消息均传递给主要所有者。然后,出现了二级所有者队列。一旦主要所有者放弃该名称,有抱负的辅助所有者队列中的顶部应用程序将负责并成为新的主要所有者。

 

消息是Dbus的IPC机制中的一个信息传递媒介。由消息头+消息体组成。消息头说明发送者,接收者,类型等。

消息有四种类型,分别是方法调用消息、结果返回消息、错误消息、信号消息。这里的信号消息是主动发送的事件,如新设备插入、文件更改的事件等。

Dbus后台是在多对多通讯时用来转发消息,管理连接的一个后台进程。每个Dbus后台都和多个连接关联,其内部维护了连接名和连接实体的映射关系。Dbus后台就象一个路由器,将从发送者连接得到的消息转发到由消息中的接收者连接名指定的接收者连接中。

Dbus后台有多个。有一个用于和系统通讯,监控系统更新事件,其类型为DBUS_BUS_SYSTEM的Dbus后台。每个桌面会话(session)有一个用于多个桌面应用之间相互通讯,其类型为DBUS_BUS_SESSION的Dbus后台。一般至少有两个Dbus后台,一个系统用,一个桌面会话用。系统用的Dbus后台只能处理系统的消息,桌面会话用的Dbus后台只能处理桌面会话应用的消息。
 

Native Objects and Object Paths
在不同的编程语言中,都定义了一些“对象”,如java中的java.lang.Object,GLIB中的GObject,QT中的QObject等等。D-BUS的底层接口,和libdbus API相关,是没有这些对象的概念的,它提供的是一种叫对象路径(object path),用于让高层接口绑定到各个对象中去,允许远端应用程序指向它们。object path就像是一个文件路径,可以叫做/org/kde/kspread/sheets/3/cells/4/5等。

Methods and Signals
每个对象都有一些成员,两种成员:方法(methods)和信号(signals),在对象中,方法可以被调用。信号会被广播,感兴趣的对象可以处理这个信号,同时信号中也可以带有相关的数据。每一个方法或者信号都可以用一个名字来命名,如”Frobate” 或者 “OnClicked”。

Interfaces
每个对象都有一个或者多个接口,一个接口就是多个方法和信号的集合。dbus使用简单的命名空间字符串来表示接口,如org.freedesktop.Introspectable。可以说dbus接口相当于C++中的纯虚类。

Bus Names
当一个应用程序连接上bus daemon时,daemon会分配一个唯一的名字给它。以冒号(:)开始,这些名字在daemon的生命周期中是不会改变的,可以认为这些名字就是一个IP地址。当这个名字映射到应用程序的连接上时,应用程序可以说拥有这个名字。同时应用可以声明额外的容易理解的名字,比如可以取一个名字com.mycompany.TextEditor,可以认为这些名字就是一个域名。其他应用程序可以往这个名字发送消息,执行各种方法。

名字还有第二个重要的用途,可以用于跟踪应用程序的生命周期。当应用退出(或者崩溃)时,与bus的连接将被OS内核关掉,bus将会发送通知,告诉剩余的应用程序,该程序已经丢失了它的名字。名字还可以检测应用是否已经启动,这往往用于只能启动一个实例的应用。

Addresses
使用d-bus的应用程序既可以是server也可以是client,server监听到来的连接,client连接到server,一旦连接建立,消息就可以流转。如果使用dbus daemon,所有的应用程序都是client,daemon监听所有的连接,应用程序初始化连接到daemon。

dbus地址指明server将要监听的地方,client将要连接的地方,例如,地址:unix:path=/tmp/abcdef表明server将在/tmp/abcdef路径下监听unix域的socket,client也将连接到这个socket。一个地址也可以指明是TCP/IP的socket,或者是其他的。

当使用bus daemon时,libdbus会从环境变量中(DBUS_SESSION_BUS_ADDRESS)自动认识“会话daemon”的地址。如果是系统daemon,它会检查指定的socket路径获得地址,也可以使用环境变量(DBUS_SESSION_BUS_ADDRESS)进行设定。

当dbus中不使用daemon时,需要定义哪一个应用是server,哪一个应用是client,同时要指明server的地址,这不是很通常的做法。

Big Conceptual Picture
要在指定的对象中调用指定的方法,需要知道的参数如下:
Address -> [Bus Name] -> Path -> Interface -> Method

 

dbus-glib编程:DBus是什么玩意


3、DBUS 配置文件
daemon 配置文件位于 /usr/share/dbus-1/system.conf   /usr/share/dbus-1/session.conf 链接到/etc/dbus-1

cinnamon-screensaver 的例子
/usr/share/dbus-1/services/org.cinnamon.ScreenSaver.service  
文件的内容是
[D-BUS Service]
Name=org.cinnamon.ScreenSaver
Exec=/usr/bin/cinnamon-screensaver     此程序当做服务 守护进程


4、使用场景
用于服务器或服务进程和客户端之间通信;
有两种形式;单向通信,客户端将事件通知服务器,但服务器不响应;服务可以广播消息给感兴趣进程,但进程不响应,这种使用signal方式
双向通信,发送消息并执行,进而返回响应消息,使用方法调用方式

5、一个客户端和服务器的例子
此示例中的服务器提供了简单的添加服务。客户端在方法调用中发送两个整数。服务器将它们加起来并将答案作为答复发送。我们将使用C程序中的低级libdbus接口。

这个例子跑下就行,不需要详细理解。

Server.c

/*
 *
 *     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