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_t
和 string
的结构体。
{
}
(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