dbus从理论到实践教程

前言

笔者最初学习dbus的时候,找了很多网上的资源,发现国内网站介绍dbus的文章,要么就是让你开通VIP才能阅读,要么就是照着英文网页翻译一下就拿过来当做自己的博客,没有自己的理解,更没有相应的代码案例。踩了很多坑,同时付出了很多时间,现将学习过程总结成博客,并且免费阅读,提供免费代码案例。

阅读本文你会得到什么?

1、对dbus有个比较清晰的认识

2、提供免费代码案例,可以令你轻松从理论到实践

源码下载地址:

https://download.csdn.net/download/slov8/90992326

dbus简介

层级结构

代码层级结构

在这里插入图片描述

运行架构

在这里插入图片描述

dbus重要概念

对一些概念不理解的,可以先大致浏览一下,留个印象,在后面的实例中会对这些概念进行更深入的解析。

Native Objects

本地对象,简单来说就是本地的类的实例化。

Object Paths

对象路径,相当于本地对象的一个绑定,可以被其他应用程序访问,其他应用程序访问本地对象,必须通过对象路径。(注意后文所称的“对象”,既指本地对象,也是对象路径,都是提供服务的一个实体)。

Methods and Signals

方法(method),相当于一个函数,可以被其他远程应用程序调用;

信号(signal),可以在一个总线上广播,关心该信号的对象,可以接收该信号。

Interfaces

一个对象可以有多个接口,一个接口是一组方法和信号的集合。

Proxies

代理,顾名思义就是远程对象的代理,通过它就可以很方便的访问远程对象。在实际编程中,可以使用代理,也可以不使用代理。

Bus Names

当一个应用程序连接到bus daemon时,其对象就会被赋予一个bus name,这个bus name是唯一的(在整个bus daemon的生命周期中),这就是unique name。它是一个以“:”冒号开头的字符串,例如“:34-907”。还有一个well-known name,它是由程序员自定义的,但是需要符合规范,例如“com.mycompany.TextEditor”。well-known name好比域名,unique name就好比IP。

Addresses

dbus地址指明server将要监听的地方,client将要连接的地方。例如system bus address为 unix:path=/var/run/dbus/system_bus_socket,session bus address为:unix:path=/run/user/1000/bus,guid=a5e28e3488d662692458da686847f032。

概念间关系

如果需要调用一个对象的方法,就需要经过以下路径:

Address -> [Bus Name] ->Object Path -> Interface -> Method

总线类型

dbus总线提供了一种软件总线抽象,每个总线也是一个守护进程。

system bus

系统总线的作用范围是整个运行的系统,所用用户都可以连接到系统总线,但是需要相应的权限。

session bus

会话总线,仅对当前登录的用户可见。

dbus消息

dbus消息类型

连接到总线上的进程,是通过message进行通信的。有4种消息类型。

Method call messages

用来调用远程对象的方法,并等待返回调用结果或者错误信息。

Method return messages

被调用的对象的方法会返回一个调用结果。

Error messages

方法调用出错时返回的消息。

Signal messages

广播消息,所用连接到总线上的进程都能接受到。信号可以包含参数,但是不会有返回值。

消息格式

消息的格式包含header和body,详见dbus的消息格式规范:https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages。

我们一般使用的是封装了libdbus的上层接口,对消息格式不敏感。

数据类型

Basic types

y (Byte): 无符号 8 位整数 (uint8_t)
b (Boolean): 布尔类型,true 或 false
n (Int16): 有符号 16 位整数 (int16_t)
q (UInt16): 无符号 16 位整数 (uint16_t)
i (Int32): 有符号 32 位整数 (int32_t)
u (UInt32): 无符号 32 位整数 (uint32_t)
x (Int64): 有符号 64 位整数 (int64_t)
t (UInt64): 无符号 64 位整数 (uint64_t)
d (Double): 双精度浮点数 (double)
s (String): UTF-8 编码的字符串
o (Object Path): 表示 D-Bus 对象路径的字符串
g (Signature): 表示 D-Bus 类型签名的字符串
h (Unix File Descriptor): 文件描述符,用于传递文件句柄

Container types

容器数据类型可以组合或者嵌套其他的类型。

a (Array): 数组类型。数组中所有元素必须是相同的数据类型。表示为 a<type>,例如 ai 表示 int32_t 的数组。

v (Variant): 可变类型,类似于 C++ 的 std::variant,可以存储任意 D-Bus 类型。
( ) (Struct): 结构类型,表示为多个不同类型的组合。类似于 C/C++ 中的结构体。例如,(is) 表示一个包含 int32_tstring 的结构体。
{ } (Dictionary/Dict Entry): 字典条目,表示键值对。通常表示为 a{key_type value_type},例如 a{su} 表示键为 string,值为 uint32_t 的字典

dbus应用实例

在写dbus应用程序时,一般不直接调用libdbus的底层接口,而是使用对libdbus进行封装的高层次的API接口库。有以下几种:

dbus-c++:使用c++封装的库,出现得比较早。

dbus-cxx:也是C++的封装库,是比较新的版本库。

Qt D-Bus:在QT中支持的dbus库。

PyDBus:python版本的库。

D-Bus for Java:java版本的库。

GDBus:基于Glib的库。

下面以dbus-c++为例来说明dbus的用法。笔者也会在文末提供完整的源码链接,并且免费下载。

DBUS-C++用法

运行环境:ubuntu20.04

软件版本:libdbus-c+±0.9.0

下载

http://downloads.sourceforge.net/project/dbus-cplusplus/dbus-c++/0.9.0

编译

笔者实践中发现,直接编译会报错,需要修改一下源码,修改的地方如下:

include/dbus-c++/eventloop-integration.h:
在这里插入图片描述
src/pipe.cpp:
在这里插入图片描述
如果还有其他报错,只需要根据报错原因修改代码就行了。

编译前先进行配置:

./configure --disable-ecore --disable-tests

编译:

make

编译完以后,在examples/echo目录下有对应的demo程序。我们先来分析代码本身的demo程序,然后自己写demo程序。

examples/echo分析

方法和信号定义

首先看echo-introspect.xml文件:
在这里插入图片描述
通过该XML文件可以定义对象的方法和信号,结合前面介绍的dbus概念,该echo示例有1个Object Paths,有1个Interfaces,有5个Methods,有1个Signals。对象路径:/org/freedesktop/DBus/Examples/Echo,接口:org.freedesktop.DBus.EchoDemo,5个方法分别是:Random、Hello、Echo、Cat、Sum、Info,1个信号:Echoed。

通过echo-introspect.xml并利用tools/dbusxx-xml2cpp工具,可以自动生成C++代码,生成代码的命令在examples/echo/Makefile文件里:

echo-server-glue.h: echo-introspect.xml
	$(top_builddir)/tools/dbusxx-xml2cpp $^ --adaptor=$@

echo-client-glue.h: echo-introspect.xml
	$(top_builddir)/tools/dbusxx-xml2cpp $^ --proxy=$@

一个是server端的代码,一个是client端的代码。

server提供了什么

再看echo-introspect.xml文件,server提供了5个方法和1信号,client可以调用这5个方法,并且接收这个信号。

从echo-server-glue.h的代码可以看到,其使用了类适配器模式,实现了接口适配器:

class EchoDemo_adaptor
: public ::DBus::InterfaceAdaptor

来分析一下echo-server.cpp的代码:

class EchoServer
  : public org::freedesktop::DBus::EchoDemo_adaptor,
  public DBus::IntrospectableAdaptor,
  public DBus::ObjectAdaptor

server继承了EchoDemo_adaptor,并且具体实现了方法和信号。

int main()
{
  signal(SIGTERM, niam);
  signal(SIGINT, niam);

  DBus::default_dispatcher = &dispatcher;

  DBus::Connection conn = DBus::Connection::SessionBus(); //连接到了session总线上
  conn.request_name(ECHO_SERVER_NAME); 

  EchoServer server(conn); // 注册自己的服务

  dispatcher.enter(); // 进入任务循环

  return 0;
}
client如何访问server的服务

echo-client-glue.h:

class EchoDemo_proxy
: public ::DBus::InterfaceProxy

echo-client.h:

class EchoClient
  : public org::freedesktop::DBus::EchoDemo_proxy,
  public DBus::IntrospectableProxy,
  public DBus::ObjectProxy

client是通过代理(proxy)来访问server的服务。

再来看一下client实现了什么功能。

int main()
{
  size_t i;

  signal(SIGTERM, niam);
  signal(SIGINT, niam);

  DBus::_init_threading();

  DBus::default_dispatcher = &dispatcher;

  // increase DBus-C++ frequency
  new DBus::DefaultTimeout(100, false, &dispatcher);

  DBus::Connection conn = DBus::Connection::SessionBus(); // 连接到session总线

  EchoClient client(conn, ECHO_SERVER_PATH, ECHO_SERVER_NAME);
  g_client = &client;

  pthread_t threads[THREADS];

  // 创建了3个管道,并为每个管道的读数据端口设置回调函数handlerX(即从管道读到数据的时候,就会调用handlerX)
  thread_pipe_list[0] = dispatcher.add_pipe(handler1, NULL);
  thread_pipe_list[1] = dispatcher.add_pipe(handler2, NULL);
  thread_pipe_list[2] = dispatcher.add_pipe(handler3, NULL);
  // 创建3个线程,每个线程都往对应的管道以字符串的形式写自己的线程ID号
  for (i = 0; i < THREADS; ++i)
  {
    pthread_create(threads + i, NULL, greeter_thread, (void *) i);
  }
  // 进入任务循环
  dispatcher.enter();

  cout << "terminating" << endl;

  for (i = 0; i < THREADS; ++i)
  {
    pthread_join(threads[i], NULL);
  }

  dispatcher.del_pipe(thread_pipe_list[0]);
  dispatcher.del_pipe(thread_pipe_list[1]);
  dispatcher.del_pipe(thread_pipe_list[2]);

  return 0;
}

greeter_thread线程会往管道里面写自己的线程ID号,dispatcher.enter()里面循环读取管道,如果能成功读到数据,就会调用handlerX。handlerX会把从管道读到的数据作为参数,调用代理Hello方法来访问server端的Hello方法,并等待调用结果。

整体调用框架

在这里插入图片描述

总结

上述例子展示了server如何通过session bus提供服务,以及client如何通过session bus调用server的method。但是如何发送信号,接收信号,如何使用系统总线(system bus)的示例,后面我们自己写一个例子来说明吧。

自己写一个demo实例:examples/demo1

demo包括server和client,展示如何通过system bus调用method、发送signal、接收并处理signal。

在example下创建demo1目录
mkdir demo1
通过xml文件定义方法和信号
<?xml version="1.0" ?>
<node name="/org/freedesktop/DBus/Examples/Demo1">
  <interface name="org.freedesktop.DBus.Demo1">
    <method name="Sum">
      <arg type="ai" name="ints" direction="in"/>
      <arg type="i" names="sum" direction="out"/>
    </method>
    <signal name="Echoed">
      <arg type="v" name="value"/>
    </signal>
    <method name="Info">
      <arg type="a{ss}" name="info" direction="out"/>
    </method>
  </interface>
</node>

生成代码,创建相应的文件

通过以下命令生成代码:

dbusxx-xml2cpp demo1-introspect.xml --adaptor=demo1-server-glue.h
dbusxx-xml2cpp demo1-introspect.xml --proxy=demo1-client-glue.h

目录结构如下:
在这里插入图片描述

编写代码

demo1-server.cpp:

#include "demo1-server.h"
#include <cstddef>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <pthread.h>
#include <iostream>

static const char *DEMO1_SERVER_NAME = "org.freedesktop.DBus.Examples.Demo1Bus";
static const char *DEMO1_SERVER_PATH = "/org/freedesktop/DBus/Examples/Demo1";

using namespace std;

DBus::BusDispatcher dispatcher;
Demo1Server *g_server = NULL;
static int32_t g_exit = 0;

Demo1Server::Demo1Server(DBus::Connection &connection)
    : DBus::ObjectAdaptor(connection, DEMO1_SERVER_PATH)
{

}

int32_t Demo1Server::Sum(const std::vector<int32_t>& ints)
{
    int32_t sum = 0;

    for (size_t i = 0; i < ints.size(); ++i) sum += ints[i];

    return sum;
}

std::map< std::string, std::string > Demo1Server::Info()
{
  std::map< std::string, std::string > info;
  char hostname[64];

  gethostname(hostname, sizeof(hostname));
  info["hostname"] = hostname;
  info["username"] = getlogin();

  return info;
}

void *signal_thread(void *arg)
{
    DBus::Variant v;
    DBus::MessageIter it = v.writer();
    it << std::string("DBus-c++ demo1");
    
    if (v.signature() == "s") {
        std::string content;
        DBus::MessageIter it_tmp;
        it_tmp = v.reader();
        it_tmp >> content;
        std::cout << "Variant content: " << content << std::endl;
    }
    // 定时发送信号给client
    while(!g_exit) {
        if (g_server) {
            g_server->Echoed(v);
        }
        sleep(2);
    }

    return NULL;
}

void niam(int sig)
{
    g_exit = 1;
    dispatcher.leave();
}

int main()
{

    pthread_t thread_id;
    signal(SIGTERM, niam);
    signal(SIGINT, niam);

    DBus::default_dispatcher = &dispatcher;

    DBus::_init_threading();

    DBus::Connection conn = DBus::Connection::SystemBus();
    conn.request_name(DEMO1_SERVER_NAME);

    Demo1Server server(conn);
    g_server = &server;

    pthread_create(&thread_id, NULL, signal_thread, NULL);

    dispatcher.enter();

    pthread_join(thread_id, NULL);

    return 0;
}

demo1-client.cpp:

#include "demo1-client.h"

#include <iostream>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <cstring>

using namespace std;

static const char *DEMO1_SERVER_NAME = "org.freedesktop.DBus.Examples.Demo1Bus";
static const char *DEMO1_SERVER_PATH = "/org/freedesktop/DBus/Examples/Demo1";

DBus::BusDispatcher dispatcher;

void niam(int sig)
{
    dispatcher.leave();
}

Demo1Client::Demo1Client(DBus::Connection &connection, const char *path, const char *name)
  : DBus::ObjectProxy(connection, path, name)
{
}

void Demo1Client::Echoed(const DBus::Variant &value)
{
    //信号处理器
    DBus::MessageIter it = value.reader();
    std::string content;

    if (value.signature() == "s") {
        it >> content;
        cout << "Echoed:" << content << endl;;
    }
}

int main()
{
    size_t i;

    signal(SIGTERM, niam);
    signal(SIGINT, niam);

    DBus::default_dispatcher = &dispatcher;

    DBus::Connection conn = DBus::Connection::SystemBus();
    Demo1Client client(conn, DEMO1_SERVER_PATH, DEMO1_SERVER_NAME);

    // 设置监听规则
    conn.add_match("type='signal',interface='org.freedesktop.DBus.Demo1',path='/org/freedesktop/DBus/Examples/Demo1',member='Echoed'");

    dispatcher.enter();
}
编译

g++ demo1-server.cpp -o demo1-server -I…/…/include/ -L…/…/src/.libs/ -ldbus-c+±1 -lpthread

g++ demo1-client.cpp -o demo1-client -I…/…/include/ -L…/…/src/.libs/ -ldbus-c+±1 -lpthread

设置权限

连接到system bus需要以root用户运行程序,并且在/etc/dbus-1/system.d目录下配置相应的权限文件,在此目录下创建mydemo1.conf文件,并填入以下内容:

<busconfig>

  <!-- ../system.conf have denied everything, so we just punch some holes -->

  <policy user="root">
    <allow own="org.freedesktop.DBus.Examples.Demo1Bus"/>
    <allow send_destination="org.freedesktop.DBus.Examples.Demo1Bus"/>
    <allow send_interface="org.freedesktop.DBus.Demo1"/>
    <allow receive_sender="org.freedesktop.DBus.Examples.Demo1Bus"/>
  </policy>


  <policy context="default">
    <allow send_destination="org.freedesktop.DBus.Examples.Demo1Bus"/>
  </policy>

</busconfig>

运行程序

切换到root用户:sudo su

设置动态库路径环境变量:export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/xx/xx/xx/libdbus-c+±0.9.0/src/.libs

运行server程序:./demo1-server

会打印出如下内容:

Variant content: DBus-c++ demo1

运行client程序:./demo1-client

每隔2秒钟就会打印如下内容:

Echoed:DBus-c++ demo1

整体调用框架

在这里插入图片描述

总结

本demo实现了server通过system bus来定时广播信号Echoed,信号也可以携带信息,client通过监听system bus来接收感兴趣的信号。要连接到system bus需要在/etc/dbus-1/system.d目录下添加权限配置,并且需要以root用户运行程序。

源码下载

https://download.csdn.net/download/slov8/90992326

dbus调试工具

d-feet用法

安装:

sudo apt-get install d-feet

直接运行:

d-feet

方法调用

以上面的examples/echo为例。

传入string类型参数

在这里插入图片描述

传入array类型参数

在这里插入图片描述

传入variant类型参数

在这里插入图片描述

busctl用法

以上述的examples/demo1为例。

1、监听server的信号:

sudo busctl monitor --match "type='signal',interface='org.freedesktop.DBus.Demo1',path='/org/freedesktop/DBus/Examples/Demo1',member='Echoed'"  --system

2、调用server的方法

sudo busctl --system call org.freedesktop.DBus.Examples.Demo1Bus /org/freedesktop/DBus/Examples/Demo1 org.freedesktop.DBus.Demo1 Sum ai 3 2 3 4

ai:表示是int32_t类型的数组

第一个3:表示带3个参数

2 3 4: 表示是具体的参数值

参考资料

https://blog.csdn.net/RopenYuan/article/details/147490592

https://dbus.freedesktop.org/doc/dbus-tutorial.html

https://dbus.freedesktop.org/doc/dbus-specification.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值