当前位置: 首页 > ops >正文

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++ frequencynew 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;}// 定时发送信号给clientwhile(!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

http://www.xdnf.cn/news/13932.html

相关文章:

  • Redis的string的底层实现原理
  • AI玩转空间和时间?后续会怎样发展?
  • 【Qt】信号与槽
  • 【SystemVerilog 2023 Std】第5章 词法约定 Lexical conventions (1)
  • 前端开发中的可访问性设计:让互联网更包容
  • 开关电源和线性电源
  • Linux搭建爬虫ip与私有IP池教程
  • 期权备兑策略选择什么价值的合约?
  • 详解Python当中的pip常用命令
  • uni-app项目实战笔记5--使用grid进行定位布局
  • Qt的Modbus协议-RTU从站实现
  • 【redis——缓存击穿】
  • 202557读书笔记|《梦里花落知多少(轻经典)》——有你在的地方才最美
  • Docker Buildx 简介与安装指南
  • AQS独占模式——资源获取和释放源码分析
  • 43 C 语言 math.h 库函数详解:绝对值、数学运算、取整舍入、分解组合、三角反三角、双曲函数及宏定义常量
  • Claude Blender
  • java集合篇(一) ---- 集合的概述
  • 低成本同屏方案:电脑 + 路由器实现 50 台安卓平板实时同屏
  • 基于React Native的HarmonyOS 5.0房产与装修应用开发
  • 个典型的 Java 泛型在反序列化场景下“类型擦除 + 无法推断具体类型”导致的隐性 Bug
  • 【Google Chrome】谷歌浏览器历史版本下载
  • 基于Three.js的交互式国风博物馆设计与实现
  • 绿叶洗发水瓶-多实体建模拆图案例
  • 如何有效开展冒烟测试
  • 提升搜索可见度的基石:标题标签设置原则与SEO效能量化分析
  • DBever工具自适应mysql不同版本的连接
  • 【论文解读】rStar:用互洽方法增强 SLM(小型语言模型) 推理能力
  • React Native【实战范例】水平滚动分类 FlatList
  • 歌曲《我的家我的国》 构建对传统主旋律单向度超越