目录
四、总线名称(Service Name 或 Bus Name)
什么是dbus
D-Bus 是一种进程间通信(IPC)和远程过程调用(RPC)机制,专门设计用于在同一台机器上运行的进程之间进行高效且易于使用的通信。它旨在用一个统一的协议取代该领域内多个竞争的 IPC 框架,这个协议特别针对满足安全的系统内进程间通信需求而量身定制。
是一个轻量级、低延迟、低开销、高可用性的进程间通信系统(IPC),它已被大多数Linux发行版所采用。
D-Bus由freedesktop.org项目提供,使用GPL许可证发行。D-Bus最主要的用途是在Linux桌面环境,为进程提供通信,同时能将Linux桌面环境和Linux内核事件作为消息传递到进程。
D-Bus是Desktop Bus的缩写,意为“桌面总线”。它是一个针对桌面环境优化的进程间通信(IPC)机制,也用于进程与内核的通信。具体来说:
- Desktop(桌面):D-Bus主要用于桌面操作系统中的通信,它使得桌面环境中的多个应用程序能够高效地相互通信。随着技术的发展,D-Bus也逐渐被引入到嵌入式系统中,但其名字仍然保留了“Desktop”这一原始称谓,以体现其起源和主要用途。
- Bus(总线):在D-Bus中,“Bus”指的是消息总线,它是一个逻辑上的通信通道。通过这个通道,不同的应用程序可以进行方法调用、发送信号和监听特定的信号等操作。D-Bus总线层由消息总线守护进程(message bus daemon)提供,该守护进程负责进程间的消息路由和传递。
- 它的名字暗示了它使用逻辑上的“总线”来实现这一功能。为了简化使用,这种通信通过一个简单的对象模型进行,该模型同时支持远程过程调用(RPC)和发布-订阅机制。
连接到D-Bus的应用程序可以查询对象的存在性,调用远程方法,并请求接收它们发出的信号的通知。D-Bus上导出的对象的API由其支持的接口定义。这些接口包含了接口支持的所有方法和信号以及它们所需的确切参数类型和数量的完整列表。对象可以同时支持任意数量的接口,这一特性可以用来实现类似于许多编程语言中提供的继承机制的效果,尽管D-Bus本身并不直接支持继承。
为了支持动态执行环境和简化集成,D-Bus包含了一个标准的自省机制,用于在运行时查询对象接口。许多动态语言的D-Bus绑定利用这一特性,避免了用户需要显式指定正在使用的远程接口。
D-Bus中的对象通过总线名称和对象路径的唯一组合进行标识。总线名称唯一标识连接到总线的客户端应用程序,而对象路径则唯一标识该应用程序内的对象。每个应用程序在连接时都会自动分配一个唯一且基本随机的总线名称。希望将对象导出到D-Bus上的应用程序可以请求拥有额外的“众所周知”的总线名称,以便其他应用程序在运行时能够简单且一致地访问它们。
D-Bus中的总线名称机制类似于动态IP地址分配与DNS名称解析服务的结合。新连接的D-Bus应用程序会被分配一个随机的总线名称(类似于随机IP地址分配)。为了防止潜在客户端需要发现那个随机总线名称才能访问应用程序导出的对象,提供应用程序可以请求额外的“众所周知”的总线名称作为其唯一名称的别名(类似于多个DNS条目解析到同一个IP地址)。例如,Gnome NetworkManager使用总线名称“org.freedesktop.NetworkManager”来导出其对象。这样,希望使用网络管理器的应用程序就无需尝试确定网络管理器在连接时分配的唯一随机名称,而只需使用这个众所周知的名称即可。
基本概念
层次架构
DBus采用三层架构设计,包括底层接口层、总线层和应用封装层。
一、底层接口层
- 主要构成:主要通过libdbus函数库提供系统使用DBus的能力。libdbus是DBus的底层接口库,为上层提供了一套标准的API。dbushttps://www.freedesktop.org/wiki/Software/dbus/
- 功能:进程通过调用libdbus库中的函数,可以发送和接收消息,实现进程间的通信。这一层是DBus通信机制的基础,为上层提供了必要的接口和功能支持。
- api说明 dbus aip详解-CSDN博客
二、总线层
-
主要构成:由DBus总线守护进程(Message Bus Daemon)提供。这个守护进程在Linux系统启动时运行,负责进程间的消息路由和传递,包括Linux内核和Linux桌面环境的消息传递。
-
功能:
- 消息路由:DBus总线守护进程能够同时与多个应用程序相连,并能把来自一个应用程序的消息路由到零个或多个其他程序。
- 消息传递:通过总线层,进程可以发送消息给其他进程,也可以接收来自其他进程的消息。这种通信方式是基于消息传递的,具有异步性和非阻塞性。
- 系统总线与会话总线:DBus总线层通常包含系统总线和会话总线两种类型。系统总线在Linux内核引导时就被装入内存,只有Linux内核、Linux桌面环境和权限较高的程序才能向该总线写入消息,以此保障系统安全性。会话总线由普通进程创建,可同时存在多条,用于进程间的私有通信。
三、应用封装层
-
主要构成:通过一系列基于特定应用程序框架的Wrapper库,将DBus的底层接口封装成友好的接口供不同开发人员使用。
-
功能:
- 封装友好接口:应用封装层为开发人员提供了易于使用的接口,使得他们可以在不深入了解DBus底层机制的情况下,轻松实现进程间的通信。
- 支持多种编程语言:DBus提供了大部分编程语言的库版本,使得开发人员可以在自己熟悉的编程环境中使用DBus进行通信。
- 对象路径与接口:在应用封装层中,每个进程都可以注册为对象,并通过对象路径(Object Path)和接口(Interface)来标识和调用其提供的方法(Methods)和信号(Signals)。
- 三方库
- libdbus-glib:这是一个基于GLib库的DBus封装库,为使用GLib的开发人员提供了方便的DBus接口。它允许开发人员利用GLib的事件循环和主循环机制,轻松地集成DBus通信功能。linux clone地址
git clone https://github.com/GNOME/glib.git
- libdbus-qt:这是一个基于Qt库的DBus封装库,为使用Qt的开发人员提供了DBus接口。它使得Qt应用程序可以轻松地与其他DBus应用程序进行通信,同时也支持Qt的信号和槽机制,方便开发人员实现事件驱动编程。
- dbus_c++
https://github.com/Pelagicore/dbus-cplusplus
此外,DBus的应用封装层还支持其他多种编程语言的库版本,如Python、C++、Java 等,使得开发人员可以在自己熟悉的编程环境中使用DBus进行通信。
总线
D-Bus总线主要分为系统总线(System Bus)和会话总线(Session Bus)两种类型,它们在功能、用途和安全性方面有着明显的区别。
系统总线(System Bus)
- 功能:
- 系统总线是D-Bus的一种形式,用于在系统层面上的进程间通信(Inter-Process Communication,IPC)。
- 它主要用于系统服务之间的交流,如硬件状态的变更、系统设置的更新等。
- 用途:
- 允许系统级的服务和进程相互交流。
- 由操作系统和后台进程使用,具有非常好的安全性,以防止任意的应用程序欺骗系统事件。
- 安全性:
- 系统总线在Linux内核引导时就会启动,并被装入内存。
- 只有Linux内核、Linux桌面环境和权限较高的程序才能向该总线写入消息,以此保障系统安全性。
会话总线(Session Bus)
- 功能:
- 会话总线是另一种D-Bus总线类型,它局限于单个用户会话。
- 它使得在同一会话中运行的应用程序能够交换信息。
- 用途:
- 允许同一用户环境下的应用程序相互交流,如共享数据、发送通知等。
- 由普通进程创建,可同时存在多条,属于某个进程私有,用于进程间传递消息。
- 安全性:
- 会话总线虽然也考虑安全性,但相对系统总线来说较为宽松,更注重用户的便捷使用。
- 进程必须注册后才能收到总线中的消息,并且可同时连接到多条总线中。
- 系统总线:主要用于系统服务之间的交流,具有全局性和较高的安全性要求。它像是一个高级别的决策者,掌握着更广泛的权力和信息。
- 会话总线:用于同一用户会话中的应用程序间通信,具有局部性和便捷性。它更像是基层工作人员,处理的是更具体、更个人化的事务。
服务
DBus服务是DBus(Desktop Bus)系统中的一种重要组件,它提供了进程间通信(IPC)的机制,允许不同的应用程序和组件之间进行通信和交换数据。DBus服务通常是由提供服务的应用程序或组件实现的,它们通过DBus总线(Message Bus)来接收和发送消息。
一、DBus服务的定义与功能
-
定义:
DBus服务是DBus系统中的一种实体,它提供了特定的功能和接口供其他进程调用。服务通常是在DBus总线上注册的进程,它们使用唯一的名称来标识自己,并暴露对象、接口和方法供客户端调用。 -
功能:
- 提供接口和方法:DBus服务通过接口暴露方法,其他进程可以通过DBus总线调用这些方法。
- 接收和处理消息:DBus服务能够接收来自其他进程的消息,并根据消息的内容执行相应的操作。
- 发送信号:DBus服务可以在特定事件发生时发送信号,通知其他对该信号感兴趣的进程。
二、DBus服务的注册与发现
-
注册:
当进程想要提供DBus服务时,它需要在DBus总线上注册自己。注册过程包括指定服务的名称、对象路径和接口等信息。DBus总线会维护一个服务列表,记录当前注册的所有服务。 -
发现:
客户端进程可以通过DBus总线查询当前注册的服务列表,以发现可用的服务。客户端还可以根据服务的名称、对象路径和接口等信息来定位特定的服务。
三、DBus服务的实现与调用
- 实现:
DBus服务的实现通常涉及以下几个步骤:- 在服务代码中实现这些接口和方法。
- 将服务注册到DBus总线上。
- 调用:
客户端进程可以通过DBus总线调用DBus服务提供的方法。调用过程包括以下几个步骤:- 连接到DBus总线。
- 获取服务的对象路径和接口。
- 调用接口中的方法,并传递必要的参数。
- 接收方法的返回值或处理可能发生的错误。
四、总线名称(Service Name 或 Bus Name)
总线名称有两种类型:唯一和众所周知的。唯一的总线名称由总线分配给每个客户端连接。它们以:开头,并保证在总线生命周期内永不再使用。与可以翻转并重复使用的进程ID不同,唯一的总线名称保证是真正唯一的。
D-Bus客户端可以请求额外的“众所周知”的总线名称,以便在约定名称下提供服务。这使得应用程序能够轻松地在已知位置找到提供的服务。例如,Gnome Network Manager在众所周知的总线名称org.freedesktop.NetworkManager上提供服务,以防止潜在用户需要确定网络管理器的唯一总线名称。
众所周知的总线名称基本上具有与DNS域名相同的命名要求(尽管总线名称可能包括_字符)。由于“众所周知”的总线名称的整个目的是简化资源命名,因此避免意外名称冲突的公认惯例是以反向DNS域名前缀众所周知的总线名称:org.frobozz.Zork
D-Bus对总线名称的所有权实行简单的仲裁机制。如果客户端请求一个未使用的名称,它将立即被授予。然而,如果名称当前被占用,客户端有两种方法可以获得名称。它可以尝试窃取所有权,或者可以将其放入队列中以最终获得所有权。
要窃取所有权,必须满足两个条件。当前拥有该名称的应用程序必须通知总线它愿意放弃该名称的所有权,并且窃取应用程序必须设置标志,以启用从拥有应用程序窃取名称的所有权请求。如果这两个条件都未满足,对名称的请求将简单地失败,并返回一个错误代码,说明该名称目前不可用。
为了排队等待所有权,请求应用程序在所有权请求中设置一个标志,指示如果名称当前不可用,请求应该排队。如果在未来的某个时刻,所有权被授予客户端,将通过信号通知这一事实。
- 定义:对象名字通常指的是服务的名称或DBus总线上的注册名称。
- 功能:服务名称用于在DBus总线上唯一标识一个服务,客户端可以通过这个名称来查找和访问服务。
对象
D-Bus 对象在应用程序内通过其对象路径进行标识。对象路径有意设计成类似于标准Unix文件系统路径的样式。主要区别在于,路径可能只包含数字、字母、下划线和斜杠(/)字符。
从功能角度来看,对象路径的主要目的仅仅是作为对象的唯一标识符。路径结构所隐含的“层次结构”几乎完全是约定俗成的。具有自然层次结构的应用程序可能会利用这一特性,而其他应用程序则可能完全忽略它。
简而言之,D-Bus 对象路径是一种用于在D-Bus上唯一标识对象的字符串,它类似于Unix文件路径,但受限于特定的字符集,并且其层次结构主要是约定性的,不一定反映对象的实际组织结构。应用程序可以根据需要选择是否利用这种层次结构来组织和管理对象。
DBus中的对象(Object)是服务内的具体实体,通常代表某种资源或功能。在DBus系统中,对象扮演着非常重要的角色,它们是进程间通信的基本单位之一。
以下是关于DBus对象的详细介绍:
一、对象的定义与功能
- 定义:DBus对象是由服务(Service)提供的,用于封装具体的资源或功能。每个服务可以注册多个对象,每个对象都有一个唯一的对象路径(Object Path)来标识。
- 功能:对象通过接口(Interface)暴露方法(Method)和信号(Signal)。方法是可以被其他进程调用的操作,用于执行特定的任务;而信号则是当某些事件发生时,对象发出的通报,其他进程可以订阅这些信号以接收通知。
二、对象的注册与访问
- 注册:当进程注册到DBus总线时,它会创建并注册相应的消息对象。这些对象通过对象路径来唯一标识,并且可以通过DBus总线被其他进程访问。
- 访问:其他进程可以通过DBus总线上的对象路径来访问这些对象,并调用它们的方法或订阅它们的信号。
三、对象的接口与实现
- 接口:每个对象都暴露一个或多个接口,接口定义了对象可以执行的操作(方法)和可以发出的通知(信号)。
- 实现:服务端代码需要实现这些接口中定义的方法和信号。当客户端通过DBus总线调用这些方法时,服务端会执行相应的代码并返回结果或发送信号。
四、对象路径(Object Path)
- 定义:对象路径是一个字符串,用于在DBus总线上唯一标识一个对象。
- 格式:对象路径通常以斜杠(/)开头,并包含多个由斜杠分隔的层级。
- 功能:通过对象路径,客户端可以定位并访问特定的DBus对象。
接口(Interface)
D-Bus 接口定义了D-Bus对象所支持的方法和信号。为了使远程用户能够使用D-Bus接口,必须让他们知道该接口。这个接口定义可以硬编码到应用程序中,也可以在运行时通过D-Bus内省机制进行查询。虽然从技术上讲是可选的,但大多数D-Bus实现都会自动为它们导出的对象提供内省支持。
D-Bus接口的命名约定与众所周知的总线名称的命名约定相似。为了减少名称冲突的机会,通常的约定是在接口名称前加上反向DNS域名作为前缀。例如,标准的“Introspection”(内省)接口是“org.freedesktop.DBus.Introspectable”。
D-Bus接口中方法和信号名称的限制与许多流行的编程语言中使用的限制相似。它们必须以字母开头,并且只能包含字母、数字和下划线。
每个方法和信号都明确定义了它们接受的参数的数量和类型。这些参数被编码为D-Bus签名字符串。D-Bus签名字符串是一种用于描述D-Bus上传递的数据类型的字符串表示法,它允许在D-Bus上传递各种复杂的数据结构。通过这种方式,D-Bus接口提供了一种灵活且强大的机制来定义和使用对象之间的通信协议。
- 定义:接口是一组方法和信号的集合,它定义了一个对象可以执行的操作和可以发出的事件。
- 功能:接口为客户端提供了一种标准化的方式来调用对象的方法和订阅对象的信号。
方法(Method)
D-Bus方法可以接受任意数量的参数,并且可以返回任意数量的值,包括零个。当方法调用没有指定返回值时,仍然会向调用应用程序发送一个“方法返回”消息。这允许使用远程API的应用程序知道远程方法调用已经完成,即使没有返回有用的结果。唯一不发送返回消息以确认D-Bus方法调用的情况是,如果作为方法调用的一部分发送了“不期望回复”的标志。这是一个可选的D-Bus实现功能,如果支持,可以用来抑制生成回复消息。方法参数列表和方法返回值的签名字符串可能包含多种类型。对于每个接受的参数和每个返回的值,参数/返回值的类型简单地按顺序追加到签名字符串中。例如,一个接受两个无符号32位整数并返回两个字符串的方法将使用"uu"作为参数签名,使用"ss"作为返回值签名。如果方法不接受任何参数或返回任何值,这些属性的签名是空字符串。
方法调用流程
1.使用不同语言绑定的dbus高层接口,都提供了一些代理对象,调用其他进程里面的远端对象就像是在本地进程中的调用一样。应用调用代理上的方法,代理将构造一个方法调用消息给远端的进程。
2.在DBUS的底层接口中,应用需要自己构造方法调用消息(method call message),而不能使用代理。
3.方法调用消息里面的内容有:目的进程的bus name,方法的名字,方法的参数,目的进程的对象路径,以及可选的接口名称。
4.方法调用消息是发送到bus daemon中的。
5.bus daemon查找目标的bus name,如果找到,就把这个方法发送到该进程中,否则,daemon会产生错误消息,作为应答消息给发送进程。
6.目标进程解开消息,在dbus底层接口中,会立即调用方法,然后发送方法的应答消息给daemon。在dbus高层接口中,会先检测对象路径,接 口,方法名称,然后把它转换成对应的对象(如GObject,QT中的QObject等)的方法,然后再将应答结果转换成应答消息发给 daemon。
7.bus daemon接受到应答消息,将把应答消息直接发给发出调用消息的进程。
8.应答消息中可以包容很多返回值,也可以标识一个错误发生,当使用绑定时,应答消息将转换为代理对象的返回值,或者进入异常。
bus daemon不对消息重新排序,如果发送了两条消息到同一个进程,他们将按照发送顺序接受到。接受进程并需要按照顺序发出应答消息,例如在多线程中处理这些消息,应答消息的发出是没有顺序的。消息都有一个序列号可以与应答消息进行配对。
- 定义:方法是接口中定义的一段函数代码,它带有输入参数和输出返回值。
- 功能:方法允许客户端通过DBus调用对象的功能,实现远程操作。
信号(Signal)
信号 D-Bus信号提供了一种一对多的发布-订阅机制。与方法返回值类似,D-Bus信号可能包含任意数量的数据。然而,与方法不同,信号是完全异步的,并且可以由D-Bus对象在任何时间发出。默认情况下,从D-Bus对象发出的信号不会发送给任何客户端。要接收信号,客户端应用程序必须明确注册他们对D-Bus对象发出的特定信号的兴趣。
信号的一般流程
1.当使用dbus底层接口时,信号需要应用自己创建和发送到daemon,使用dbus高层接口时,可以使用相关对象进行发送,如Glib里面提供的信号触发机制。
2.信号包含的内容有:信号的接口名称,信号名称,发送进程的bus name,以及其他参数。
3.任何进程都可以依据”match rules”注册相关的信号,daemon有一张注册的列表。
4.daemon检测信号,决定哪些进程对这个信号感兴趣,然后把信号发送给这些进程。
5.每个进程收到信号后,如果是使用了dbus高层接口,可以选择触发代理对象上的信号。如果是dbus底层接口,需要检查发送者名称和信号名称,然后决定怎么做。
- 定义:信号是对象在特定事件发生时发出的一种通知,它可以带有数据。
- 功能:信号允许对象通知其他进程某个事件的发生,实现事件驱动的通信机制。
消息
消息是DBus(Desktop Bus)进程间通信的基本单位。DBus是一种在Linux和Unix系统中广泛应用的进程间通信(IPC)机制,它提供了一种可靠的消息传递系统,使得不同进程之间可以相互交换数据和调用方法。DBus消息由发送者发送到DBus总线上,然后由目标进程接收和处理。
一、DBus消息的类型
DBus消息主要分为以下几种类型:
- 方法调用消息(MethodCall):此类消息用于触发一个函数调用。当某个进程想要调用另一个进程中的方法时,它会发送一个方法调用消息到DBus总线上。
- 方法返回消息(MethodReturn):当方法调用执行完毕后,被调用的进程会返回一个方法返回消息给调用者。这个消息包含了方法的执行结果。
- 错误消息(Error):如果在方法调用过程中发生错误,被调用的进程会发送一个错误消息给调用者。这个消息包含了错误信息和错误代码。
- 信号消息(Signal):信号消息可以看作是一个事件通知。当一个进程想要通知其他进程某个事件发生时,它会发送一个信号消息到DBus总线上。其他对该信号感兴趣的进程可以订阅并接收这个信号。
二、DBus消息的组成
DBus消息由以下几个部分组成:
- 消息头(Header):消息头包含了消息的元数据,如消息类型、发送者、接收者、消息ID等。
- 消息体(Body):消息体包含了消息的具体内容,如方法调用的参数、方法的返回值、信号的数据等。
三、DBus消息的发送和接收
- 发送消息:发送者通过DBus总线发送消息。在发送消息之前,发送者需要连接到DBus总线,并指定消息的接收者、消息类型和内容等。DBus总线会将消息传递到指定的接收者。
- 接收消息:接收者通过DBus总线接收消息。在接收消息之前,接收者需要注册到DBus总线上,并指定它可以接收的消息类型和接收者地址等。当DBus总线上有匹配的消息时,它会将消息传递给接收者。
- 消息根据目标地址和匹配规则路由到客户端连接。当消息的目标参数包含唯一或众所周知的总线名称时,使用目标地址路由。这通常是方法调用和返回消息的情况,这些消息本质上需要一对一的通信。另一方面,信号是广播消息,没有特定的目标。对于这些信号,客户端应用程序必须注册匹配规则才能接收到他们感兴趣的信号。尽管信号注册是消息匹配规则最常见的用途,但DBus消息匹配规则可用于请求传递通过总线传输的任何消息;包括任意总线客户端之间的方法调用和返回消息。通过匹配规则传递的消息始终是副本,因此无法使用此机制将消息重定向离开其预期目标。消息匹配规则是通过org.freedesktop.DBus.AddMatch方法设置的,并格式化为包含在单个字符串中的一系列表示逗号分隔的键=值对。排除的键表示匹配每个消息的通配符匹配。如果匹配规则的所有组件都匹配一个消息,它将被传递给请求的应用程序。下表提供了可能在匹配规则中指定的键和值的简洁描述。有关完整细节,请参阅DBus规范。
- route element
四、基于二进制数据
D-Bus具备自身的协议,协议基于二进制数据设计,与数据结构和编码方式无关。该协议无需对数据进行序列化,保证了信息传递的高效性。无论是libdbus,还是D-Bus总线守护进程,均不需要太大的系统开销。
关系简要总结
在DBus系统中,服务、对象、对象路径、对象名字(通常指服务名称或总线名称)、接口、方法和信号之间存在着紧密的关系。
- 服务包含对象:每个DBus服务可以包含多个对象,这些对象通过不同的对象路径进行区分。
- 对象暴露接口:每个对象可以暴露一个或多个接口,这些接口定义了对象可以执行的操作和可以发出的事件。
- 接口包含方法和信号:每个接口包含一组方法和信号,客户端可以通过调用方法或订阅信号来与对象进行交互。
- 服务名称与对象路径:服务名称用于在DBus总线上唯一标识一个服务,而对象路径用于在服务中唯一标识一个对象。客户端可以通过服务名称和对象路径来定位并访问特定的DBus对象。
示例1(glib)
#include <gio/gio.h>
#include <glib.h>
#define OBJECT_PATH "/com/example/Hello"
#define INTERFACE_NAME "com.example.Hello"
#define OBJECT_PATH_1 "/com/example/hi"
#define INTERFACE_NAME_1 "com.example.hi"
static void hello_method(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GVariant **ret_val,
gpointer user_data) {
// 这是一个方法,返回一个简单的字符串
g_print("Method '%s' called\n", method_name);
*ret_val = g_variant_new("(s)", "Hello, D-Bus!");
}
static void hello_signal(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data) {
// 这是一个信号,发送 "Hello" 字符串
g_print("Signal '%s' emitted\n", signal_name);
}
static void hi_method(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GVariant **ret_val,
gpointer user_data) {
// 这是一个方法,返回一个简单的字符串
g_print("Method '%s' called\n", method_name);
*ret_val = g_variant_new("(s)", "hi, D-Bus!");
}
static void hi_signal(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data) {
// 这是一个信号,发送 "Hello" 字符串
g_print("Signal '%s' emitted\n", signal_name);
}
int main(int argc, char *argv[]) {
GDBusConnection *connection;
DBusError error;
GMainLoop *loop;
GDBusMethodTable method_table[] = {
{ "HelloMethod", NULL, hello_method },
{ NULL, NULL, NULL } // 方法表结尾标志
};
GDBusSignalTable signal_table[] = {
{ "HelloSignal", NULL, hello_signal },
{ NULL, NULL, NULL } // 信号表结尾标志
};
GDBusMethodTable method_table1[] = {
{ "HelloMethod", NULL, hi_method },
{ NULL, NULL, NULL } // 方法表结尾标志
};
GDBusSignalTable signal_table1[] = {
{ "HelloSignal", NULL, hi_signal },
{ NULL, NULL, NULL } // 信号表结尾标志
};
// 初始化错误
dbus_error_init(&error);
// 设置 D-Bus 连接
connection = g_dbus_setup_bus(DBUS_BUS_SESSION, "com.example.sayService", &error);
if (dbus_error_is_set(&error)) {
g_print("Failed to set up DBus connection: %s\n", error.message);
return 1;
}
// 注册接口
if (!g_dbus_register_interface(connection,
OBJECT_PATH,
INTERFACE_NAME,
method_table,
signal_table,
NULL, // 没有属性
NULL, // 没有额外数据
NULL)) {
g_print("Failed to register interface\n");
return 1;
}
// 注册接口
if (!g_dbus_register_interface(connection,
OBJECT_PATH_1 ,
INTERFACE_NAME_1,
method_table1,
signal_table1,
NULL, // 没有属性
NULL, // 没有额外数据
NULL)) {
g_print("Failed to register interface\n");
return 1;
}
// 启动一个主循环,等待方法调用和信号发送
loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
// 清理资源
dbus_error_free(&error);
g_main_loop_unref(loop);
return 0;
}
说明:
-
g_dbus_setup_bus
:创建并返回一个 D-Bus 连接。它将服务连接到指定的 D-Bus 总线上,注册服务名称"com.example.sayService"
。DBUS_BUS_SESSION
指的是会话总线(应用程序级别的总线),你也可以使用DBUS_BUS_SYSTEM
来连接系统总线。connection = g_dbus_setup_bus(DBUS_BUS_SESSION, "com.example.HelloService", &error);
-
g_dbus_register_interface
:将接口、方法和信号注册到指定的对象路径上。这里我们注册了接口com.example.Hello
,它包含了一个方法HelloMethod
和一个信号HelloSignal
g_dbus_register_interface(connection, OBJECT_PATH, INTERFACE_NAME, method_table, signal_table, NULL, NULL, NULL);
上述示例调用
g_dbus_setup_bus注册了一个名字为
com.example.sayService的服务,接着调用g_dbus_register_interface 在服务上面添加了两个对象,对象路径分别是/com/example/Hello,/com/example/Hi,同时在/com/example/Hello对象上面添加了接口com.example.Hello,在/com/example/Hello对象上面添加了接口com.example.Hi.接着在接口com.example.Hello上面添加了method_table方法数字,signal_table信号数组,接口com.example.Hi上面添加了method_table1方法数字,signal_table1信号数组.
图示如下:
示例2(libdbus)
服务(提供方法调用 ,信号处理)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>
void reply_to_method_call(DBusMessage *msg, DBusConnection *conn)
{
DBusMessage *reply;
DBusMessageIter arg;
char *param = NULL;
dbus_bool_t stat = TRUE;
dbus_uint32_t level = 2010;
dbus_uint32_t serial = 0;
if (!dbus_message_iter_init(msg, &arg))
printf("Message has noargs\n");
else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
printf("Arg is notstring!\n");
else
dbus_message_iter_get_basic(&arg, ¶m);
if (param == NULL)
return;
reply = dbus_message_new_method_return(msg);
dbus_message_iter_init_append(reply, &arg);
if (!dbus_message_iter_append_basic(&arg, DBUS_TYPE_BOOLEAN, &stat))
{
printf("Out ofMemory!\n");
exit(1);
}
if (!dbus_message_iter_append_basic(&arg, DBUS_TYPE_UINT32, &level))
{
printf("Out ofMemory!\n");
exit(1);
}
if (!dbus_connection_send(conn, reply, &serial))
{
printf("Out of Memory\n");
exit(1);
}
dbus_connection_flush(conn);
dbus_message_unref(reply);
}
void listen_dbus()
{
DBusMessage *msg;
DBusMessageIter arg;
DBusConnection *connection;
DBusError err;
int ret;
char *sigvalue;
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "ConnectionError %s\n", err.message);
dbus_error_free(&err);
}
if (connection == NULL)
{
printf("connection failed\n");
return;
}
ret = dbus_bus_request_name(connection, "test.wei.dest", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Name Error%s\n", err.message);
dbus_error_free(&err);
}
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
{
printf("DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER\n");
return;
}
dbus_bus_add_match(connection, "type='signal',interface='test.signal.Type'", &err);
dbus_connection_flush(connection);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Match Error%s\n", err.message);
dbus_error_free(&err);
}
while (1)
{
dbus_connection_read_write(connection, 0);
msg = dbus_connection_pop_message(connection);
if (msg == NULL)
{
sleep(1);
continue;
}
if (dbus_message_is_signal(msg, "test.signal.Type", "Test"))
{
printf("receive signal\n");
if (!dbus_message_iter_init(msg, &arg))
fprintf(stderr, "Message Has no Param");
else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
;
else
dbus_message_iter_get_basic(&arg, &sigvalue);
printf("receive signal data =%s\n",sigvalue);
}
else if (dbus_message_is_method_call(msg, "test.method.Type", "Method"))
{
printf("receive method call\n");
//我们这里面先比较了接口名字和方法名字,实际上应当现比较路径
if (strcmp(dbus_message_get_path(msg), "/test/method/Object") == NULL)
reply_to_method_call(msg, connection);
}
dbus_message_unref(msg);
}
}
int main(int argc, char **argv)
{
listen_dbus();
return 0;
}
方法调用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>
//建立与session D-Bus daemo的连接,并设定连接的名字,相关的代码已经多次使用过了
DBusConnection *connect_dbus()
{
DBusError err;
DBusConnection *connection;
int ret;
//Step 1: connecting session bus
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "ConnectionErr : %s\n", err.message);
dbus_error_free(&err);
}
if (connection == NULL)
return NULL;
//step 2: 设置BUS name,也即连接的名字。
ret = dbus_bus_request_name(connection, "test.wei.source", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Name Err :%s\n", err.message);
dbus_error_free(&err);
}
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return NULL;
return connection;
}
void send_a_method_call(DBusConnection *connection, char *param)
{
DBusError err;
DBusMessage *msg;
DBusMessageIter arg;
DBusPendingCall *pending;
dbus_bool_t *stat;
dbus_uint32_t *level;
dbus_error_init(&err);
//针对目的地地址,请参考图,创建一个method call消息。Constructs a new message to invoke a method on a remote object.
msg = dbus_message_new_method_call("test.wei.dest", "/test/method/Object", "test.method.Type", "Method");
if (msg == NULL)
{
//g_printerr("MessageNULL");
return;
}
//为消息添加参数。Appendarguments
dbus_message_iter_init_append(msg, &arg);
if (!dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, ¶m))
{
//g_printerr("Out of Memory!");
exit(1);
}
//发送消息并获得reply的handle。Queues amessage to send, as withdbus_connection_send() , but also returns aDBusPendingCall used to receive a reply to the message.
if (!dbus_connection_send_with_reply(connection, msg, &pending, -1))
{
//g_printerr("Out of Memory!");
exit(1);
}
if (pending == NULL)
{
//g_printerr("Pending CallNULL: connection is disconnected ");
dbus_message_unref(msg);
return;
}
dbus_connection_flush(connection);
dbus_message_unref(msg);
//waiting a reply,在发送的时候,已经获取了methodreply的handle,类型为DBusPendingCall。
// block until we recieve a reply, Block until the pendingcall is completed.
dbus_pending_call_block(pending);
//get the reply message,Gets thereply, or returns NULL if none has been received yet.
msg = dbus_pending_call_steal_reply(pending);
if (msg == NULL)
{
fprintf(stderr, "ReplyNull\n");
exit(1);
}
// free the pendingmessage handle
dbus_pending_call_unref(pending);
// read the parameters
if (!dbus_message_iter_init(msg, &arg))
fprintf(stderr, "Message hasno arguments!\n");
else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_BOOLEAN)
fprintf(stderr, "Argument isnot boolean!\n");
else
dbus_message_iter_get_basic(&arg, &stat);
if (!dbus_message_iter_next(&arg))
fprintf(stderr, "Message hastoo few arguments!\n");
else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_UINT32)
fprintf(stderr, "Argument isnot int!\n");
else
dbus_message_iter_get_basic(&arg, &level);
printf("Got Reply: %d,%d\n", stat, level);
dbus_message_unref(msg);
}
int main(int argc, char **argv)
{
DBusConnection *connection;
connection = connect_dbus();
if (connection == NULL)
return -1;
send_a_method_call(connection, "Hello, D-Bus");
return 0;
}
信号发送
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include <dbus-1.0/dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <unistd.h>
int send_a_signal(char *sigvalue)
{
DBusError err;
DBusConnection *connection;
DBusMessage *msg;
DBusMessageIter arg;
dbus_uint32_t serial = 0;
int ret;
//步骤1:建立与D-Bus后台的连接
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "ConnectionErr : %s\n", err.message);
dbus_error_free(&err);
}
if (connection == NULL)
{
printf("connect fail\n");
return -1;
}
//步骤2:给连接名分配一个well-known的名字作为Bus name,这个步骤不是必须的,可以用if 0来注释着一段代码,我们可以用这个名字来检查,是否已经开启了这个应用的另外的进程。
#if 1
ret = dbus_bus_request_name(connection, "test.singal.source", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if (dbus_error_is_set(&err))
{
fprintf(stderr, "Name Err :%s\n", err.message);
dbus_error_free(&err);
}
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
return -1;
#endif
//步骤3:发送一个信号
//根据图,我们给出这个信号的路径(即可以指向对象),接口,以及信号名,创建一个Message
if ((msg = dbus_message_new_signal("/test/signal/Object", "test.signal.Type", "Test")) == NULL)
{
fprintf(stderr, "MessageNULL\n");
return -1;
}
//给这个信号(messge)具体的内容
dbus_message_iter_init_append(msg, &arg);
if (!dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &sigvalue))
{
fprintf(stderr, "Out OfMemory!\n");
return -1;
}
//步骤4: 将信号从连接中发送
while (1)
{
if (!dbus_connection_send(connection, msg, &serial))
{
fprintf(stderr, "Out of Memory!\n");
return -1;
}
sleep(1);
}
dbus_connection_flush(connection);
printf("Signal Send\n");
//步骤5: 释放相关的分配的内存。
dbus_message_unref(msg);
return 0;
}
int main(int argc, char **argv)
{
send_a_signal("Hello,LIU PING PING \n");
return 0;
}
编译脚本
MAKEFILE=Makefile
BINC=receiveSignal
BINS=methodservice
LIBPATH := -L/home/userdata/dbus/dbus/dbusdemo/dbus/
LIBPATH1 := -L/home/userdata/dbus/dbus/dbusdemo/glib/.libs
LIBNAME= -ldbus-1
LIBNAME1=-ldbus-glib-1
INCS=-I/opt/host/include/dbus-1.0 \
-I/opt/host/include/glib-2.0 \
-I/opt/host/lib/glib-2.0/include \
-I/opt/host/lib/dbus-1.0/include
SRCS:=methodservice.c
target:=methodserviceTT
#endif
#SRCS:=$(wildcard *.c)
COBJS:=$(SRCS:.c=.o)
#$(LIBS)
all:$(target)
$(COBJS) : %.o: %.c
g++ -c $< -o $@ $(INCS) $(CFLAGS)
$(target):$(COBJS)
g++ -o $(target) $(COBJS) $(CFLAGS) $(LIBPATH) $(LIBNAME) $(LIBPATH1) $(LIBNAME1)
rm $(COBJS)
clean:
rm $(BIN) $(COBJS)
数据类型
D-Bus 使用一种基于字符串的类型编码机制,称为“签名”(Signatures),来描述方法和信号所需的参数的数量和类型。签名用于接口声明/文档、数据封送(marshalling)和有效性检查。签名的字符串编码采用了一种简单但富有表现力的格式,对于有效使用D-Bus来说,需要对其有一个基本的了解。下表列出了基本类型及其编码字符:
类型说明图示:
在签名字符串中,不允许有任何空格。当为多参数方法和签名定义签名时,每个参数的类型会被连接成一个单一的字符串。例如,一个接受两个整数后跟一个字符串的方法的签名将是“iis”。
容器类型
有四种容器类型:结构体(Structs)、数组(Arrays)、变体(Variants)和字典(Dictionaries)。
结构体
结构体由括号括起来,并且可以包含任何有效的D-Bus签名。例如,(ii) 定义了一个包含两个整数的结构体,而((ii)s) 定义了一个包含一个包含两个整数的结构体后跟一个字符串的结构体。不允许有空的结构体。
数组
数组定义了一个由具有固定类型的成员组成的列表。数组字符“a”后面必须紧跟数组中的数据类型。这必须是一个单一且完整的类型。
示例
- ai - 32位整数的数组
- a(ii) - 结构体的数组
- aai - 整数数组的数组
变体
变体可以包含任何类型的值。变体的封送值包括定义了它所包含数据类型的D-Bus签名。
字典
字典的工作方式与结构体类似,但仅限于键=值对的数组。键必须是基本类型且不能是容器类型,而值可以是任何单一且完整的类型。字典是根据使用{}包围键和值类型的数组来定义的。示例:
- a{ss} - 字符串⇒字符串(即字符串到字符串的映射)
- a{is} - 32位有符号整数⇒字符串
- a{s(ii)} - 字符串⇒包含两个整数的结构体
- a{sa{ss}} - 字符串⇒字符串到字符串的字典(即字符串到另一个字典的映射)
这些签名规则确保了D-Bus上的数据交换能够正确地进行类型检查和解释。
通信
DBus通信使用的套接字是本地套接字。DBus作为一种高级的进程间通信(IPC)机制,在Linux系统中被广泛应用。它基于本地套接字实现,允许不同进程之间进行高效且安全的通信。以下是使用本地套接字作为DBus通信基础的好处:
- 低延迟和低开销:
- 本地套接字在同一台机器上进行通信,因此避免了网络通信中的延迟和开销。
- DBus使用二进制协议,减少了序列化的代价,进一步提高了通信效率。
- 安全性和可靠性:
- 本地套接字通信发生在本地系统内部,减少了外部攻击的风险。
- DBus提供了结构化的名字空间和独立于架构的数据格式,增强了通信的稳定性和兼容性。
- 易于管理和维护:
- 本地套接字通信不涉及复杂的网络配置和路由,使得系统更容易管理和维护。
- DBus提供了丰富的API和工具,如d-feet,用于查看和管理DBus会话和系统总线上的服务。
- 支持多种通信模式:
- DBus支持点对点和广播消息,允许应用程序既可以直接与特定程序通信,也可以向所有订阅者广播消息。
- 这为开发者提供了灵活的通信选项,满足了不同应用场景的需求。
- 高效的数据传输:
- 本地套接字支持高效的数据传输,减少了数据传输过程中的瓶颈和延迟。
- DBus通过优化消息结构和处理流程,进一步提高了数据传输的效率。
- 跨平台兼容性:
- 虽然DBus主要在Linux系统中使用,但它也支持其他类Unix操作系统,如BSD和Solaris。
- 这使得DBus成为一种跨平台兼容的IPC机制,为开发者提供了更广泛的开发选项。