前言
笔者最初学习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++ 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

3858

被折叠的 条评论
为什么被折叠?



