DBUS简介

学习网址:​​https://dbus.freedesktop.org/doc/dbus-tutorial.html​

DBUS三层:
libdbus,消息分发的守护进程Dbus daemon,应用程序框架的封装库或绑定 (For example, libdbus-glib and libdbus-qt)

libdbus仅仅支持one-to-one connection

关于传递,对象是message = header (filed) + body (param)
header常包括body中的数据类型信息。

DBUS的应用场景:
同一会话中的桌面应用程序
桌面应用程序和OS(系统级别的守护进程)

DBUS可以解决传统Linux IPC不能应付的远端系统管理员问题:

in current Linux support is that policies with any sort of
dynamic "interact with user" component aren't currently
supported. For example, that's often needed the first time a network
adapter or printer is connected, and to determine appropriate places
to mount disk drives. It would seem that such actions could be
supported for any case where a responsible human can be identified:
single user workstations, or any system which is remotely
administered.


This is a classic "remote sysadmin" problem, where in this case
hotplugging needs to deliver an event from one security domain
(operating system kernel, in this case) to another (desktop for
logged-in user, or remote sysadmin). Any effective response must go
the other way: the remote domain taking some action that lets the
kernel expose the desired device capabilities. (The action can often
be taken asynchronously, for example letting new hardware be idle
until a meeting finishes.) At this writing, Linux doesn't have
widely adopted solutions to such problems. However, the new D-Bus
work may begin to solve that

DBUS和其他IPC相比具有的特点:

异步处理的二进制协议

安全可靠的连接

消息总线是守护进程

通信机制的部署与实现都是明确的

和DCOP通信机制类似

安全特性,支持系统消息

Object Path
是用于让上层绑定找到native object(比如QObject,GObject)

接口
DBus使用了字符串定义来表示不同的interface,接口包括了信号和方法。

代理
proxy object用来代表远端进程中的对象。
代理给开发者简化了不少工作量。开发者可以使用代理调用远端对象的方法并得到返回值,这就像是调用本地方法一样简单。
伪代码比较:

this:

Message message = new Message("/remote/object/path", "MethodName", arg1, arg2);
Connection connection = getBusConnection();
connection.send(message);
Message reply = connection.waitForReply(message);
if (reply.isError()) {

} else {
Object returnValue = reply.getReturnValue();
}


Programming with proxies might look like this:

Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path");
Object

Bus name
当应用程序连接上bus daemon, daemon会立刻给该应用程序分配一个独一无二的connection name。它通常是以冒号开头。
与之对应,我们可以定义一个容易识别的domain name,与dbus name对应。

Dbus Address
指明server在哪里监听,client在哪里连接
一般,我们使用Dbus Daemon来分发消息,所以address由环境变量决定。但也可以重新定义自己的server,这样的话需要指明依赖于server name上的通信机制。

概念图:Address -> [Bus Name] -> Path -> [Interface] -> Method
被方括弧包围的对象是开发者不定义要提供的,需要分情况判断。

4种类型的message
call message(调用object的函数),return message(返回计算的结果),error message(调用函数发生异常),signal message(emit signal时产生)
在调用远端方法的过程中,DBUS有两种message发挥着重要作用,分别是call message和return message。在call message中包含着一种序列号,return message具有相同的序列号,这样就具备了匹配的作用。
call message包括:远端进程的bus name,函数名,函数参数,object path,接口名(可选)

发送信号
Dbus也能发送信号,通过绑定,会有接收者来作出反应。

QTDBUS 学习案例:remotecontrolledcar

学习网址:​​http://doc.qt.io/qt-5/qtdbus-index.html​

D-Bus Concept

Analogy

Name format

Service name

Network hostnames

Dot-separated (“looks like a hostname”)

Object path

URL path component

Slash-separated (“looks like a path”)

Interface

Plugin identifier

Dot-separated

例子:
remotecontrolledcar
object path:/com/trollech/examples/car
interface name:org.example.Examples.CarInterface
服务端注册服务:

;
connection.registerObject("/Car", car);
connection.registerService("org.example.CarExample");

XML产生接口

<node name="/com/trollech/examples/car">
<interface name="org.example.Examples.CarInterface">
<method name="accelerate"/>
<method name="decelerate"/>
<method name="turnLeft"/>
<method name="turnRight"/>
<signal name="crashed"/>
</interface>
</node>

Qt D-Bus XML compiler 是 qdbusxml2cpp,这两者的产出:the interface (proxy) class or the adaptor class(服务端)
interface:
pro文件加上 ​​​DBUS_ADAPTORS += car.xml​​​
产生头文件 ​​​qdbusxml2cpp -p car_interface.h: car.xml​​​
产生cpp文件 ​​​qdbusxml2cpp -i car_interface.h -p :car_interface.cpp car.xml​​​
adaptor:
pro文件加上 ​​​DBUS_INTERFACES += car.xml​​​
调用相关的命令
产生头文件 ​​​qdbusxml2cpp -a car_adaptor.h: car.xml​​​
产生cpp文件 ​​​qdbusxml2cpp -i car_adaptor.h -a :car_adaptor.cpp car.xml​

客户端 访问远程的对象:

#include "car_interface.h"

car = new org::example::Examples::CarInterface("org.example.CarExample", "/Car",
QDBusConnection::sessionBus(), this);

关于CarInterface:

typedef ::OrgExampleExamplesCarInterfaceInterface CarInterface;

/*
* Proxy class for interface org.example.Examples.CarInterface
*/
class OrgExampleExamplesCarInterfaceInterface: public QDBusAbstractInterface

OrgExampleExamplesCarInterfaceInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);
//QDBusAbstractInterface 提供了D-Bus的接口,用于本地访问远端的接口。

效果:

在mac上貌似没有dbus-daemon,需要自己下载,配置
我安装后的目录是 ​​​/usr/local/Cellar/dbus/1.12.8/​​​
Mac上由Launchd管理守护进程,有点像linux中的init。
为了让dbus-daemon成为守护进程,我们需要将她的service plist文件放到Launchd的配置目录中。
这些目录有:

# 登陆后程序启动
~/Library/LaunchAgents
/Library/LaunchAgents
/System/Library/LaunchAgents

# 系统启动后程序启动
/Library/LaunchDaemons
/System/Library/LaunchDaemons

dbus安装目录下面有 org.freedesktop.dbus-session.plist
label string即之前提到过的service name

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.freedesktop.dbus-session</string>

<key>ProgramArguments</key>
<array>
<string>/usr/local/Cellar/dbus/1.12.8/bin/dbus-daemon</string>
<string>--nofork</string>
<string>--session</string>
</array>

<key>Sockets</key>
<dict>
<key>unix_domain_listener</key>
<dict>
<key>SecureSocketWithKey</key>
<string>DBUS_LAUNCHD_SESSION_BUS_SOCKET</string>
</dict>
</dict>
</dict>

他可以被放在:​​/Users/weiyang/Library/LaunchAgents​​​
我的mac上lantern是开机自启的,所以我仿照​​​org.getlantern.plist​​​在ProgramArguments的​​</array>​​​下面
加上了如下的key。

<key>RunAtLoad</key>
<true/>

并将她的文件属性改成:

然后重启登陆,可以发现有了dbus-daemon

然后mac上也能运行了: