D-Bus Tutorial

D-Bus Tutorial

What is D-Bus?

D-Bus是一个用于进程间通信的系统。从架构上来说,分为三部分:

  • 一个libdbus库,允许两个app连接,交换信息
  • 一个基于libdbus的daemon,各种app可以连接到daemon。Daemon将信息从一个app路由到其他apps。
  • 基于app framework的封装库或者绑定。比如,libdbus-glib和libdebus-qt。也有针对语言的绑定,比如Python。大多开发者直接使用的是这些封装库,这些库声明了Dbus相关的编程细节。libdbus作为底层后台,用来支持上层的bindings。libdbus的很多API只对binding的实现有效。

libdbus只支持one-to-one类型的连接,如同原始套接字那样,区别是,发送的不是字节流(byte streams)而是消息。消息包含消息header和body,header标明信息类型,body包含消息主体内容。 Libdbus也可以允许实现特定的传输通道,从而来完成比如像认证之类的应用细节。

消息总线守护进程将D-bus上连接的所有程序构成一个轮形hub。轮子上的每一个轮辐都是基于libdbus的一对一连接,连接了一个app。app通过轮辐向daemon发送消息,daemon在合适的时机将消息转发到其他已连接的app。daemon可以理解为一个路由器。

总线守护进程在系统中有多个实例。比如全局唯一的实例,一个如同sendmail或者Apache的系统daemon,对于接收什么样消息用来进程间通信,有着很严格的安全要求(system bus?)。另外一些daemon实例由用户登录时建立,用于该用户的session中的app之间通信。

系统级的daemon和per-user daemon是分离的。session内的IPC不会影响系统级的message bus,反之亦然。

D-Bus applications

有许多技术用来实现IPC或者网络信息传递,比如:CORBA, DCE, DCOM, DCOP, XML-RPC, SOAP, MBUS, Internet Communications Engine (ICE)。每一种都是针对特定种类的应用程序量身定做的。
Dbus为两种情况而生:

  • 同一desktop session中的app之间信息传递,这样就将session视为一个整体,解决进程生命周期的问题。
  • desktop session与操作系统之间的信息传递,即session和kernel或者其他系统daemon之间的通信。

Concepts

图片描述

Native Objects and Object Paths

开发者在自己的编程架构中会定义object,通常基于一个基础的类,比如 java.lang.Object, GObject, QObject, python's base Object, or whatever. 我们称为native object。

dbus协议和libdbus中的api不关心native object如何定义,而是提供了object path的概念。有了object path,上层的bindings就能够命名native object实例,并且允许远程的app引用这些object。
object path如同文件系统中的文件路径。比如,一个object可以被命名为/org/kde/kspread/sheets/3/cells/4/5。易于理解的路径很棒,如有需要的话,是你也可以命名为诸如 /com/mycompany/c5yo817y0c1y1c5b。
名称中指定命名空间是明智的做法,比如将路径开头设置为你开发的domain名称。这样可以保证同一进程中的不同模块互不干扰。

Methods and Signals

每个object都有成员(members),成员分为方法(methods)和信号(signals)。方法(methods)是object可以调用的一组操作,带有输入或者输出参数。信号被广播到任何一个对该信号感兴趣的对象;信号也可以承载数据。

引用方法或者信号都是通过它们的名字,比如Frobate或者OnClicked

Interfaces

一个object只是一个或多个interface。一个interface中包含了一组方法和信号(methods and signals),概念如同 GLib or Qt or Java中的方法和信号。接口定义了object实例的类型。
dbus用简单的命名空间字符串标识一个interface,比如org.freedesktop.Introspectable;大多数绑定会将这些interface直接映射到对应的编程语言结构,比如Java接口或者C++的纯虚类。

Proxies

代理(proxy)object是native object,用于代表其他进程中的远程object。底层的DBus API创建一个Methods call,发送,然后接收,处理这些回复的消息。可以把代理看做是一个普通的native object,但是当你调用代理中的方法,绑定(bindings)将该操作转化为
dbus Method call消息,等待远端回复消息,将返回值解包,然后返回到native 方法......

以下是一个伪代码的例子,不使用proxy:

  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();
  }

使用proxy

  Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path");
  Object returnValue = proxy.MethodName(arg1, arg2);

Bus Names

每当app连接dbus daemon时,daemon为app分配一个唯一的连接名字,以冒号开头。一个bus name永远指向同一个app,不会被复用。例如:34-907。冒号后面的数字除了它们的唯一性没有意义。
app也可以请求便于理解的名字。比如,com.mycompany.TextEditor。对应的object的路径可以为/com/mycompany/TextFileManager,interface为org.freedesktop.FileHandler

可以将:34-907(unique name)当作是IP地址,com.mycompany.TextEditor当作是域名。com.mycompany.TextEditor映射 :34-907就如沟通mycompany.com映射到192.168.0.5。

除了路由消息,bus name还可用来追踪app生命周期。当一个app退出或者crash时,操作系统内核(operating system kernel)会断开message bus的连接,然后message bus发送消息通知
其他app该name已经失去owner了。通过追踪这些notification,可以检测其他app的生命周期。

Bus name名字还可以检测应用是否已经启动,这可以用来实现单实例启动程序。

Addresses

应用作为dbus的server或者client,server监听来自client的连接,client连接到server。一旦连接建立,它就是一个对称的消息流。
Dbus address指明server去哪监听,client去哪里连接。例如,地址unix:path=/tmp/abcdef指明了server将监听一个Unix socket,路径是/tmp/abcdef, client将连接到这个socket。
address也能指定TCP/IP sockets,或者其他类型的传输方式。

Big Conceptual Picture

把上面的概念组合起来得到以下流程:

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

Message

Dbus工作原理是在两个进程之间发送消息,如果工作在相当上层的binging中,是不会直接处理消息的。
四种类型消息:

  • Method call消息,用来调用一个object的一个方法
  • message return消息,返回一个Method的处理结果
  • 错误消息,返回method调用中的exception
  • 信号消息,通知一个指定信号触发了,可以理解成事件触发
  • method call消息对应一个message return消息或者一个错误消息

每个消息有一个消息头,包含field和body和桉树。可以将header当作消息的路由信息,body当作消息体。header可能包含以下信息:发送方bus name,目的地的busname,调用的方法或者信号,等等。
header中有一个描述body中值的类型的field。比如,字母i岱庙32位整数,ii标识body有两个32位整数。

Calling a Method

一个方法调用(method call)由两条消息组成,一个方法调用消息从进程A发送给进程B,对应的方法返回消息从进程B发送给进程A,发送和返回消息都要经过bus daemon路由。
每个call消息包含一串独有的数字,reply消息也包含这个数字以便和发送方匹配。

发送消息带有参数,会传递给远程method,回复消息可能包含一个错误或者远程method返回的数据。

Dbus中的方法调用流程如下:

  • 语言相关的绑定可能提供一个代理(proxy),调用一个本进程object内的方法,实际上是调用远程进程的object上的一个方法。这种情况下,代理上会组织一个方法调用消息发送到远程进程。
  • 对于底层的API,应用可以自己组织一个方法调用消息,不使用proxy。
  • 方法调用消息会包含以下信息:远程进程的bus
    name;方法名称;方法需要的参数;远程进程的object路径;包含method的接口(interface)(可选)
  • 方法调用消息被发送到bus daemon
  • bus daemon根据目的地bus name找到远程进程,将消息发往目标进程,若找不到,则返回错误
  • 接收方进程对消息进行解包。简单的使用底层API的情况下,可能立即调用方法并返回结果到bus
    daemon。当时用上层绑定时,binding可能检查object路径,接口,方法,然后将method call消息转换为对本地object(GObject, java.lang.Object, QObject,etc.)的调用,然后将本地方法的返回值转换为方法的返回消息。
  • Bus daemon收到方法的返回消息然后发送给method call的发送方
  • 发送方收到reply消息,解析数据,也可能返回错误。当使用绑定时,返回消息会被转换成代理方法(proxy
    method)的返回值或者exception。

Bus daemon不会为消息排序,也就是说,如果发送两条method call消息到同一接收方,接收的顺序与发送顺序相同。接收方不一定按照接收的顺序来回复消息。比如,接收方可能在两个不同的线程中处理method call,哪个消息先处理完就先返回。

Emitting a Signal

信号由一条消息组成,从一个进程发送到另一个或多个进程。也就是说,信号是单向的广播。信号可能包含参数(数据部分),因为它是一个广播,没有返回值。

信号的发送者不知道接受者是谁。接收者为了接收信号需要向bus daemon进行注册,基于“match rules”,这些规则包含了发送者和接受者的名字。Bus daemon只将信号发送给对该信号感兴趣的接收者。

信号的流程大致如下:

  • 当使用dbus底层接口时,信号需要应用自己创建和发送到daemon,使用dbus高层接口时,可以使用相关对象进行发送,如Glib里面提供的信号触发机制。
  • 信号包含的内容有:信号的接口名称,信号名称,发送进程的bus name,以及其他参数。
  • 任何进程都可以依据”match rules”注册相关的信号,daemon有一张注册的列表。
  • daemon检测信号,决定哪些进程对这个信号感兴趣,然后把信号发送给这些进程
  • 每个进程收到信号后,如果是使用了dbus高层接口,可以选择触发代理对象上的信号。如果是dbus底层接口,需要检查发送者名称和信号名称,然后决定怎么做。

Introspection

Dbus objects支持org.freedesktop.DBus.Introspectable,dbus的标准接口,无需参数,返回一个XML字符串,描述了接口,方法,信号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值