简介

Linux Dbus是由freedestop.org项目开发的一款IPC进程间通讯技术,它是基于在Unix 域套接字 (unix domain sockets-UDS)实现的,它在sockets上面进行了封装并提供了一套更加规范、方便、安全的通讯机制,同时定义了一套标准接口,目前已经被大量Linux发行版所采用,比如Ubuntu、Centos...

Dbus最初的目的就是为桌面应用程序之间提供IPC通讯技术

它不属于Linux内核的一部分,它目前没有被Linux内核所采纳

概念

Bus (总线)

总线是Dbus的核心思路,应用程序通过监听总线来获取另外一个应用程序发送的消息。

类似广播,监听某一频段信号来获取广播消息。

Dbus提供了两套总线:System Bus (系统总线)、Sevice Bus (服务总线)分别对应不同的权限。

System Bus:只有Linux内核和权限比较高的应用程序才能访问

Sevice Bus:为普通应用程序提供访问线路

System Bus和Sevice Bus都是由守护进程提供服务,由DBus daemon的进程提供服务。

Linux内核启动时最后一步由Systemd启动DBus daemon,System Bus和Sevice Bus分别对应两个不同的DBus daemon守护进程,启动顺序是:System Bus、Sevice Bus。

Object (对象)

在Dbus中每一个连接到总线的应用程序都具有一个或一组对象,当应用程序连接到总线时会向总线注册一个对象,总线通过对象与其它应用程序进行交互,对象包含了接口、消息类型等属性,应用程序可以通过总线来调用某个对象的属性来完成一次进程间的交互。

对象是Dbus下重要的概念,任何一个监听发送都应是一个对象。

在Dbus中对象的命名也有要求,虽然没有强制要求但建议命名方式是以路径的方式命名,主要目的是为了区分模块,例如一个大型的软件里可能存在许多模块,为了更好的分开降低耦合性每个单独的模块如果需要进行IPC通讯应单独定义一个对象,例如:

"/net/mqtt/sensor"

上面是基于mqtt协议的传感器模块命名,你可以根据上面的规范来定义你的对象名称,虽然没有强制规定这么做但Dbus官方强烈建议这么命名,这么做的好处可以降低耦合性。

对象名在全局里是唯一的,不可以重复。

对象类型

Dbus对象提供了四种类型:

  1. signals 信号

  1. method calls 方法调用

  1. method returns 方法返回

  1. errors 错误

signals信号

信号是对象的属性之一,信号主要是用来传递消息或命令,应用程序可以通过监听感兴趣的信号来进行收听,类似广播。

method calls方法调用

方法调用就是函数的调用,对象需要定义方法调用属性,应用程序可以通过调用对象的这个消息类型来完成一次远程函数调用,同时方法调用支持传递参数。

method returns 方法返回

在完成一次函数调用之后,对象想要返回执行结构可以通过这个消息类型将执行结果返回。

errors 错误

当产生错误的时候或遇到了未知的异常错误可以通过这个消息类型来返回告知调用者。

对象副本

在Dbus里每次传输都会将对象的副本完全传输过去,所以接收方可以通过副本来获取到更多尽可能详细的信息,发送方里设置的所有属性都会被接收方完整的接收到,不光只包含数据部分。

Inerface (接口)

接口在Dbus里可以理解为集合,接口定义了对象的方法、信号所属于那一种接口类型,一个对象可以定义多个接口,接口的目的是为了将对象里的方法、信号做分类,一个对象可能会定义多个方法、信号,那么可以对它们做分类,规划到接口里去。

例如一个对象有A接口和B接口,可以在这两个接口里定义同一名称的方法或消息,因为它们处于不同的接口里所以不会出现命名重复的问题。

通常情况下应用程序调用对象方法时是不需要指定接口的,但如果对象定义了多个重复名称的方法在不同的接口里,如A接口和B接口都有一个getname的方法,这个时候应用程序就需要指定接口了。

可以把接口理解为对象下某一组方法、消息的类型。

接口的命名与对象命名一致,应明确表面含义,但不使用路径命名法,因为它属于对象的一部分。

"net.mqtt.sensor"

Proxie (代理)

代理是Dbus下提出的一个封装层技术,目前有许多库对Dbus进行了封装,比如GNU-Dbus、Systemd-Dbus,它们都是基于Dbus的API进行了一层封装,在封装之上它提供了一个中间层,就是代理,比如GNU-Dbus,它会在应用程序里新建一个Bus Servcer用于做转发,它将应用层与Dbus的总线分割开了,应用层无法直接访问Dbus,而是通过GNU-Dbus提供的总线来做转发,对象的维护与总线的访问都由GNU-Dbus来进行维护而非应用程序。

当使用第三方封装库时如果封装库使用了这种概念技术,应称之为代理。

Bus Name (总线名称)

应用程序对象连接到总线时,Dbus会为其分配一个名称,名称由Dbus内部定义,名称格式为:

:number-number

比如当对象连接到总线时Dbus会为其分配一个私有的名称为":34-907",当然这些名称也是可以获取的但是可读性并不高,应用程序可以为其指定一个名称,但私有名称仍然会存在,应用程序指定的名称是字符串类型的,可以自由定义,这个名称应作为通讯时所使用的名称,应尽量不使用私有名称。

通讯协议

Dbus使用的是二进制协议,而非文本协议,但它发送时仍然需要进行组包这一序列化的过程,但优点是它内部使用域套接字通讯,域套接字不会进行二次组包、封装等序列化的过程,仅仅执行内存拷贝的工作。

Dbus通过数据结构定义了一套二进制协议类型:DBusMessage通过这个协议发送数据到总线在由总线转发给监听者。

/**
 * @brief Internals of DBusMessage *
 * Object representing a message received from or to be sent to
 * another application. This is an opaque object, all members
 * are private.
 */
struct DBusMessage
{
  DBusAtomic refcount; /**< Reference count */

  DBusHeader header; /**< Header network data and associated cache */

  DBusString body;   /**< Body network data. */

  unsigned int locked : 1; /**< Message being sent, no modifications allowed. */

#ifndef DBUS_DISABLE_CHECKS
  unsigned int in_cache : 1; /**< Has been "freed" since it's in the cache (this is a debug feature) */
#endif  DBusList *counters;   /**< 0-N DBusCounter used to track message size/unix fds. */  long size_counter_delta;   /**< Size we incremented the size counters by.   */

  dbus_uint32_t changed_stamp : CHANGED_STAMP_BITS; /**< Incremented when iterators are invalidated. */

  DBusDataSlotList slot_list;   /**< Data stored by allocated integer ID */

#ifndef DBUS_DISABLE_CHECKS
  int generation; /**< _dbus_current_generation when message was created */
#endif

#ifdef HAVE_UNIX_FD_PASSING
  int *unix_fds;
  /**< Unix file descriptors associated with this message. These are
     closed when the message is destroyed, hence make sure to dup()
     them when adding or removing them here. */
  unsigned n_unix_fds; /**< Number of valid fds in the array */
  unsigned n_unix_fds_allocated; /**< Allocated size of the array */

  long unix_fd_counter_delta; /**< Size we incremented the unix fd counter by */
#endif
};

它与文本协议不同的是,例如JSON传输时是文本协议,当接收方接收到了以后还需要在进行反序列化到数据结构里才能使用,它比文本协议少了一次序列化的过程,但并不意味着它本身就不会去做二进制协议的序列化,例如数据结构里有指针,它需要将指针里的值取出来放入到Buff里然后在发送出去,这一步骤也是序列化。

Dbus最大一次只能发送32KB的数据。

两大机制

Signals 信号

基本概念

信号是Dbus下一种通讯方式,它类似于广播,任何应用程序都可以监听或发送消息类型。

应用程序通过向总线注册感兴趣的信号事件类型,当总线收到对应信号类型时会转发给所有感兴趣的应用程序,发送的信号可以带有参数,应用程序可以通过过滤参数来决定那些信号是自己感兴趣的,同时应用程序收到信号不会有任何返回。

通讯过程

  1. 应用程序向Dbus注册感兴趣的信号对象

  1. Dbus收到对应信号之后转发给所有感兴趣的应用程序

Method (方法)

基本概念

Method是Dbus下一种远程调用的方式,通过Method,Dbus运行一个应用程序去调用另外一个应用程序里的函数并支持传递参数以及返回值。

在对象类型里提到过两个对象类型:Method callMethod return,这两个类型分别对应调用与返回,call由调用者发起而return由执行者返回执行结果。

如果产生了异常错误,执行者也可以返回error对象告知调用者本次调用不合法或产生了异常错误。

调用过程

  1. 调用者向Dbus发送一次Method调用

  1. Dbus将调用转发给执行者

  1. 执行者检查调用是否合法,如果不合法则应返回error

API文档

简介

Dbus具有许多封装,从开始所说Dbus是基于域套接字实现的,任何开发者都可以通过域套接字协议来对Dbus进行封装而非使用Dbus的API,Dbus官方提供了一个Lib库:libdbus。

除此之外还有别的封装库:gnu-dbus、systemd-dbus

gnu-dbus、systemd-dbus都是基于libdbus实现的,gnu-dbus使用service bus总线,而systemd-dbus使用的是system bus,两者权限不同。

libdbus是Dbus最底层的Lib库,它的使用相较于gnu-dbus、systemd-dbus更加复杂,它被定义为低级dbus lib库,而gnu-dbus、systemd-dbus定义为高级dbus架构库,除此之外还有QT、Python等都提供了对libdbus的封装,这些封装称之为Dbus绑定。

这些高级库用起来会更加轻松与简单,本文以libdbus做介绍,目的是为了更接近dbus的原理。

libdbus是Dbus官方提供的Lib库,它没有任何代理是直接与dbus daemon做通讯。

下载地址:Download

官方开发文档:DOC

文档基于DBUS1.15.4

数据结构

BusData

消息总线相关数据块

DBus8ByteStruct

一个8字节的结构,可以在不支持int64的情况下访问int64

DBusAddressEntry

数据库总线地址

DBusAllocatedSlot

用于存储分配的数据

DBusArrayLenFixup

如果需要调整数组长度;这些调整将由该结构描述

DBusAtomic

原子结构,用于递减操作

DBusAuth

数据库总线身份验证

DBusAuthClient

客户端DBusAuth的“子类”

DBusAuthCommandName

从命令名到枚举的映射

DBusAuthMechanismHandler

表示特定身份验证机制的虚拟表

DBusAuthServer

客户端DBusAuth的“子类”

DBusAuthStateData

有关身份验证状态的信息

DBusBabysitter

实施细节

DBusBasicValue

一个简单的值联合,允许您访问字节,就好像它们是各种类型一样;通过void指针和varargs处理基本类型时很有用

DBusCMutex

Dbus互斥锁

DBusCondVar

总线条件

DBusConnection

总线连接

DBusCounter

总线计数器

DBusCredentials

总线凭据

DBusDataSlot

用于存储连接上的应用程序数据

DBusDataSlotAllocator

总线数据分配器

DBusDataSlotList

在给定元素中存储实际用户数据集的数据结构

DBusDirIter

Internals of directory iterator

DBusError

表示异常的对象

DBusFreedElement

用于表示空闲链表中的一个元素

DBusGroupInfo

有关UNIX组的信息

DBusGUID

全球唯一的ID;每个DBusServer都有一个,每个安装了libdbus的机器也有一个

DBusHashEntry

总线哈希条目

DBusHashIter

哈希迭代器对象

DBusHashTable

总线哈希表

DBusHeader

消息头数据及其一些缓存的详细信息

DBusHeaderField

缓存的有关消息中标头字段的信息

DBusKey

cookie文件中的单个密钥

DBusKeyring

总线钥匙环

DBusList

链接列表中的节点

DBusMemBlock

DBusMemBlock对象表示单个malloc()返回的块,该块被分块为内存池中的对象

DBusMemPool

内存池总线

DBusMessage

总线消息

DBusMessageFilter

表示消息筛选函数的内部结构

DBusMessageIter

DBusMessageIter结构体;不包含公共字段

DBusMessageIter_1_10_0

dbus 1.10.0中堆栈上DBusMessageIter的布局

DBusMessageLoader

总线消息加载器

DBusMessageRealIter

总线消息RealIter

DBusNonceFile

内部使用,未公开

DBusObjectPathVTable

总线对象路径V表

DBusObjectSubtree

表示单个已注册子树处理程序的结构,或是已注册子树处理器的父节点

DBusObjectTree

总线对象树

DBusPendingCall

总线挂起呼叫

DBusPipe

内部使用,未公开

DBusPollFD

内部使用,未公开

DBusPreallocatedSend

总线预分配

DBusRealError

总线实际错误

DBusRealHashIter

总线真实哈希迭代器

DBusRealString

总线实际字符串

DBusRMutex

内部使用,未公开

DBusServer

总线服务器

DBusServerSocket

总线服务器套接字

DBusServerVTable

总线服务器路径V表

DBusSHAContext

SHA算法的结构存储状态

DBusSignatureIter

总线签名迭代器

DBusSignatureRealIter

总线签名迭代器

DBusSocket

套接字接口

DBusStat

具有stat()结果的可移植结构

DBusString

内部使用,未公开

DBusThreadFunctions

必须实现的函数才能使D-Bus库线程感知

DBusTimeout

总线超时

DBusTimeoutList

总线超时列表

DBusTransport

总线传输

DBusTransportSocket

总线传输套接字

DBusTransportVTable

总线传输V表

DBusTypeReader

类型读取器是一个迭代器,用于从值块中读取值

DBusTypeReaderClass

用于类型读取器的虚拟表

DBusTypeWriter

类型写入器是一个迭代器,用于写入值块

DBusUserInfo

有关UNIX用户的信息

DBusVariant

一种不透明的数据结构,包含任何单个D总线消息项的序列化形式,其签名为单个完整类型

DBusWatch

总线监视器

DBusWatchList

总线监视列表

HeaderFieldType

内部使用,未公开

ReplacementBlock

内部使用,未公开

ShutdownClosure

此结构表示关闭时要调用的函数

接口

由于API过多,这里只挑常用的介绍,具体可以参考官方文档

消息总线API

dbus_bus_get

函数原型

DBUS_EXPORT DBusConnection *dbus_bus_get (DBusBusType type, DBusError *error)

函数作用

连接到一个总线守护进程并向其注册客户端。

参数介绍

  • type 总线类型可以是DBUS_BUS_SESSION或DBUS_BUS_SYSTEM

  • error 可以返回错误的地址。

函数返回值

返回连接句柄

dbus_bus_add_match

函数原型

DBUS_EXPORT void dbus_bus_add_match(DBusConnection *connection, 
                                    const char *rule, DBusError *error)

函数作用

添加匹配规则以匹配通过消息总线的消息,即向总线添加监听感兴趣的事件。

参数介绍

  • connection 连接句柄

  • rule 匹配规则的文本形式

  • error 存储任何错误的位置

dbus_bus_request_name

函数原型

DBUS_EXPORT int dbus_bus_request_name(DBusConnection *connection, 
                                      const char *name, 
                                      unsigned int flags, DBusError *error)

函数作用

通过调用总线上的RequestName方法,请求总线将给定的名称分配给该连接。

参数介绍

  • connection 连接句柄

  • name 要请求的名称

  • flags 标志

  • error 存储错误的位置

函数返回值

结果代码,如果设置了错误,则为-1

dbus_connection_unref

函数原型

DBUS_EXPORT void dbus_connection_unref(DBusConnection *connection)

函数作用

递减DBusConnection的引用计数,并在计数达到零时将其终结。

参数介绍

  • connection 连接句柄

使用dbus_bus_get创建的连接是由Dbus服务来维护的,不可以调用dbus_connection_close函数来进行关闭, 应调用dbus_connection_unref来递减引用,这个函数会向Dbus总线服务传递递减引用,当Dbus总线那边检测到引用为0时会自动关闭这个连接

消息API

dbus_message_new_signal

函数原型

DBUS_EXPORT DBusMessage *dbus_message_new_signal(const char *path, 
                                                 const char *iface,
                                                 const char *name)    

函数作用

构造表示信号发射的新消息。

参数介绍

  • path 对象名,以路径命名

  • iface 信号接口名

  • name 信号的名称

函数返回值

返回信号类型的对象句柄

dbus_message_append_args

函数原型

DBUS_EXPORT dbus_bool_t dbus_message_append_args(DBusMessage *message, 
                                                 int first_arg_type,
                                                 ...)

函数作用

在给定变量参数列表的消息中追加字段。

参数介绍

  • message 消息句柄

  • first_arg_type 第一个参数的类型

  • ... 第一个参数的值,附加类型值对的列表

示例

在压入参数时需要给定类型,Dbus内部会做二进制协议的序列化

基本变量类型示例

dbus_int32_t v_INT32 = 42;
const char *v_STRING = "Hello World";
dbus_message_append_args (message,
                          DBUS_TYPE_INT32, &v_INT32,
                          DBUS_TYPE_STRING, &v_STRING,
                          DBUS_TYPE_INVALID);

数组示例

const dbus_int32_t array[] = { 1, 2, 3 };
const dbus_int32_t *v_ARRAY = array;
dbus_message_append_args (message,
                          DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &v_ARRAY, 3,
                          DBUS_TYPE_INVALID);

函数返回值

成功返回TRUE

dbus_message_iter_init

函数原型

DBUS_EXPORT dbus_bool_t dbus_message_iter_init(DBusMessage *message, 
                                               DBusMessageIter *iter)

函数作用

初始化DBusMessageIter以读取传入消息的参数。

参数介绍

  • message 消息句柄

  • iter 指向要初始化的迭代器的指针

函数返回值

失败返回FALSE

dbus_message_iter_init_append

函数原型

DBUS_EXPORT void dbus_message_iter_init_append(DBusMessage *message,
                                               DBusMessageIter *iter)    

函数作用

初始化DBusMessageIter,以便将参数附加到消息的末尾。

参数介绍

  • message 消息句柄

  • iter 指向要初始化的迭代器的指针

dbus_message_iter_append_basic

函数原型

DBUS_EXPORT dbus_bool_t dbus_message_iter_append_basic(DBusMessageIter *iter,
                                                       int type, 
                                                       const void *value )    

函数作用

将基本类型的值附加到消息中。

  • iter 追加迭代器

  • type 值的类型

  • value 值的地址

dbus_message_iter_get_arg_type

函数原型

DBUS_EXPORT int dbus_message_iter_get_arg_type(DBusMessageIter *iter)    

函数作用

返回消息迭代器所指向的参数的参数类型。

参数介绍

  • iter 迭代器句柄

函数返回值

参数类型

Dbus类型可以参考文档: TYPE
dbus_message_iter_get_basic

函数原型

DBUS_EXPORT void dbus_message_iter_get_basic(DBusMessageIter *iter,
                                             void *value)    

函数作用

从消息迭代器中读取基本类型值。

参数介绍

  • iter 迭代器句柄

  • value 存储值的位置

dbus_message_unref

函数原型

DBUS_EXPORT void dbus_message_unref(DBusMessage *message)  

函数作用

递减DBusMessage的引用计数,如果计数达到0,则释放该消息。

参数介绍

  • message 消息句柄

dbus_message_new_method_call

函数原型

DBUS_EXPORT DBusMessage *dbus_message_new_method_call(const char *destination, 
                                                      const char *path,
                                                      const char *iface,
                                                      const char *method)

函数作用

构造一个新消息来调用远程对象上的方法。

参数介绍

  • destination 消息应发送到的bus名称或NULL

  • path 消息应发送到的对象路径

  • iface 调用方法的接口,或NULL

  • method 要调用的方法

函数返回值

返回方法类型的消息对象

dbus_message_new_method_return

函数原型

DBUS_EXPORT DBusMessage *dbus_message_new_method_return(DBusMessage *method_call)

函数作用

构造一条消息,该消息是对方法调用的答复。

参数介绍

  • method_call 要答复的调用对象句柄

函数返回值

返回答复类型的消息对象

dbus_message_is_signal

函数原型

DBUS_EXPORT dbus_bool_t dbus_message_is_signal(DBusMessage *message,
                                               const char *iface,
                                               const char *signal_name)    

函数作用

检查消息是否为信号类型并且是否具有指定接口与信号名

参数介绍

  • message 消息句柄

  • iface 接口类型

  • signal_name 信号名称

函数返回值

如果符合指定类型则返回True

连接API

dbus_connection_send

函数原型

DBUS_EXPORT dbus_bool_t dbus_connection_send(DBusConnection *connection, 
                                             DBusMessage *message, 
                                             dbus_uint32_t *serial)    

函数作用

将消息添加到传出消息队列里,注意是添加到发送队列里,不是立即发送。

参数介绍

connection 连接句柄

message 消息句柄

serial 存储插入到消息队列里的位置

函数返回值

失败返回FALSE

dbus_connection_flush

函数原型

DBUS_EXPORT void dbus_connection_flush(DBusConnection *connection)

函数作用

阻塞并等待消息发送出去

参数介绍

  • connection 连接句柄

dbus_connection_add_filter

函数原型

DBUS_EXPORT dbus_bool_t dbus_connection_add_filter(
                                      DBusConnection *connection, 
                                      DBusHandleMessageFunction function,
                                      void *     user_data,
                                      DBusFreeFunction     free_data_function)

函数作用

添加消息筛选器,并绑定一个回调函数。

应用程序可以添加多个筛选器,当发生信号时会将信号传递给筛选器,由筛选器按顺序调用执行。

参数介绍

  • connection 连接对象句柄

  • function 处理消息的函数

  • user_data 要传递给函数的用户数据

  • free_data_function 用于释放用户数据的函数

函数返回值

成功返回True,失败返回False

dbus_connection_read_write_dispatch

函数原型

DBUS_EXPORT dbus_bool_t dbus_connection_read_write_dispatch(
                            DBusConnection *connection,
                            int timeout_milliseconds)    

函数作用

阻塞函数,筛选器的调度函数

参数介绍

  • connection 连接句柄

  • timeout_milliseconds 最大阻塞时间或-1表示无限

函数返回值

超时或完成一次调度返回True,当连接断开返回False

dbus_connection_read_write

函数原型

DBUS_EXPORT dbus_bool_t dbus_connection_read_write(DBusConnection *connection,
                                                int     timeout_milliseconds)    

函数作用

阻塞函数,当产生读取或写入请求时阻塞会结束

参数介绍

connection 连接句柄

timeout_milliseconds 最大阻塞时间或-1表示无限

函数返回值

如果产生写入或读取请求且处于连接状态返回True

dbus_connection_pop_message

函数原型

DBUS_EXPORT DBusMessage *dbus_connection_pop_message(DBusConnection *connection)

函数作用

返回传入消息队列中接收到的第一条消息,并将其从队列中删除。

参数介绍

  • connection 连接句柄

函数返回值

返回连接句柄

dbus_message_get_args

函数原型

DBUS_EXPORT dbus_bool_t dbus_message_get_args(DBusMessage *message,
                                              DBusError *error,
                                              int first_arg_type,
                                              ...)    

函数作用

从给定变量参数列表的消息中获取参数。

参数介绍

message 消息句柄

error 存储错误的地址

first_arg_type 第一个参数类型

... 第一个参数值的位置,然后是类型-位置的列表

函数返回值

False代表错误

错误API

dbus_error_init

函数原型

DBUS_EXPORT void dbus_error_init(DBusError *error)    

函数作用

初始化DBusError结构。

参数介绍

  • error DBusError结构体地址

dbus_error_is_set

函数原型

DBUS_EXPORT dbus_bool_t dbus_error_is_set(const DBusError *error)  

函数作用

判断是否发生错误

参数介绍

  • error DBusError结构体地址

函数返回值

如果发生错误则为True

dbus_error_free

函数原型

DBUS_EXPORT void dbus_error_free(DBusError *error)  

函数作用

释放DBusError。

参数介绍

  • error DBusError结构体地址

共享连接

需要值得注意的是,当使用dbus_bus_get或dbus_message_new_signal这一类API注册到Dbus Server时这一类连接都称为共享连接,这些连接不由调用这些接口的应用程序维护,而是有Dbus维护,调用者不可以调用close或free这一类函数来释放掉,应调用unref这一类函数来递减引用次数,当引用次数为零时Dbus会自动释放掉这个连接。

实战

准备工作

首先将libdbus下载下来以后执行如下命令:

./configure --prefix=/usr/local/dbus #输出文件输出到/usr/local/dbus目录下
make
sudo make install

一次简单的信号通讯

监听端

#include <dbus/dbus.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
static DBusHandlerResult
filter_func(DBusConnection *connection, DBusMessage *message, void *usr_data)
{
    dbus_bool_t handled = false;
    char *word = NULL;
    DBusError dberr;
     
    //判断是否为信号,且接口类型是否为client.signal.Type和信号名称是否为Test
    if (dbus_message_is_signal(message, "client.signal.Type", "Test")) {
        //初始化dberr用于存储错误
        dbus_error_init(&dberr);
        //从消息里获取参数
        dbus_message_get_args(message, &dberr, DBUS_TYPE_STRING,
            &word, DBUS_TYPE_INVALID);
        //判断是否发生错误
        if (dbus_error_is_set(&dberr)) {
            dbus_error_free(&dberr);
        } else {
            printf("receive message %s\n", word);
            handled = true;
        }
    }
    return (handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
}
 
int main(int argc, char *argv[])
{
    DBusError dberr;
    DBusConnection *dbconn;
 
    dbus_error_init(&dberr);

    //连接到总线
    dbconn = dbus_bus_get(DBUS_BUS_SESSION, &dberr);
    if (dbus_error_is_set(&dberr)) {
        dbus_error_free(&dberr);
        return -1;
    }
 
    //添加筛选器
    if (!dbus_connection_add_filter(dbconn, filter_func, NULL, NULL)) {
        return -1;
    }
     
    //向总线添加感兴趣的消息类型
    dbus_bus_add_match(dbconn, "type='signal',interface='client.signal.Type'", &dberr);
    if (dbus_error_is_set(&dberr)) {
        dbus_error_free(&dberr);
        return -1;
    }
 
    //筛选器轮询函数
    while(dbus_connection_read_write_dispatch(dbconn, -1)) {
        /* loop */
    }
    return 0;
}

发送端

#include <dbus/dbus.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int db_send(DBusConnection *dbconn)
{
    DBusMessage *dbmsg;
    char *word;
    int i;
    
    //创建一个信号类型的对象
    //对象路径名为:/client/signal/Object
    //接口类型:client.signal.Type
    //信号名:Test
    dbmsg = dbus_message_new_signal("/client/signal/Object", 
                                  "client.signal.Type",     
                                  "Test");                  
    if (!dbmsg) {
        return -1;
    }
     
    //将字符串类型的参数压入信号
    word = "hello world";
    if (!dbus_message_append_args(dbmsg, DBUS_TYPE_STRING, &word, DBUS_TYPE_INVALID)) {
        return -1;
    }
 
    //将信号对象添加到发送队列里
    if (!dbus_connection_send(dbconn, dbmsg, NULL)) {
        return -1;
    }
    //阻塞等待发送结束
    dbus_connection_flush(dbconn);
    printf("send message %s\n", word);
 
    //解除引用
    dbus_message_unref(dbmsg);
    return 0;
}
 
int main(int argc, char *argv[])
{
    DBusError dberr;
    DBusConnection *dbconn;
    int i;
 
    //初始化Dbuserror
    dbus_error_init(&dberr);
 
    //获取总线
    dbconn = dbus_bus_get(DBUS_BUS_SESSION, &dberr);
    //判断是否失败
    if (dbus_error_is_set(&dberr)) {
        return -1;
    }
 
    //发送信号
    db_send(dbconn);
 
    //解除引用
    dbus_connection_unref(dbconn);
 
    return 0;
}

编译

gcc -o server -I/usr/local/dbus/include/dbus-1.0/ -I/usr/local/dbus/lib/dbus-1.0/include -L/usr/local/dbus/lib/ recv.c -ldbus-1
gcc -o send -I/usr/local/dbus/include/dbus-1.0/ -I/usr/local/dbus/lib/dbus-1.0/include -L/usr/local/dbus/lib/ send.c -ldbus-1

编译完成之后先执行server在执行send,可以看到server的输出:

receive message hello world

一次简单的方法调用

监听端

#include <stdio.h>  
#include <stdlib.h>  
#include <string.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 = 2023;  
    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, &param);  
    if(param == NULL) return;  
  
    //创建返回消息reply  
    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;  
  
    //初始化dbuserror
    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)  
        return;  

    //给连接设置一个名字test.wei.dest
    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)  
        return;  
  
    while(true){
        //获取事件
        dbus_connection_read_write(connection, -1);  
        //从队列里取一个连接对象
        msg = dbus_connection_pop_message (connection);  
      
        //判断是否为method调用
        //首先判断对象路径是否为/test/method/Objec
        if(strcmp(dbus_message_get_path(msg),"/test/method/Object") == 0) {
            //判断调用接口类型是否为test.method.Type
            //判断方法名称是否为"Method"
           if(dbus_message_is_method_call(msg,"test.method.Type","Method")){
               //都符合的情况下调用函数
               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.h>  
#include <unistd.h>  
  
DBusConnection * connect_dbus(){  
    DBusError err;  
    DBusConnection * connection;  
    int ret;

    //初始化dbuserror
    dbus_error_init(&err);  
    
    //连接到Dbus总线
    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;  
  
    //给连接设置一个名字: test.wei.source
    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);  
    }  

    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;     
    
    //初始化dbuserror
    dbus_error_init(&err);  
  
    //创建一个方法调用类型的对象
    //要调用的对象连接总线名为:test.wei.dest
    //对象路径名为:/test/method/Object
    //接口类型为:test.method.Type
    //方法名: Method
    msg = dbus_message_new_method_call ("test.wei.dest",
        "/test/method/Object",
        "test.method.Type", 
        "Method"); 
   if(msg == NULL){  
        printf("MessageNULL");  
        return;  
    }  
  
    //为消息添加参数。Appendarguments  
    dbus_message_iter_init_append(msg, &arg);  
    if(!dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING,&param)){  
        printf("Out of Memory!");  
        exit(1);  
    }  
  
    //发送一个需要回答的方法调用,如果不需要回答请使用dbus_connection_send
    if(!dbus_connection_send_with_reply (connection, msg,&pending, -1)){  
        printf("Out of Memory!");  
        exit(1);  
    }       
  
    if(pending == NULL){  
        printf("Pending CallNULL: connection is disconnected ");  
        dbus_message_unref(msg);  
        return;  
    }  
  
    //阻塞等待发送完成
    dbus_connection_flush(connection);  
    dbus_message_unref(msg);  
    
    //阻塞等回答响应
    dbus_pending_call_block (pending);  
    //获取响应 
    msg = dbus_pending_call_steal_reply (pending);  
    if (msg == NULL) {  
        fprintf(stderr, "ReplyNull\n");  
        exit(1);  
    }  
    // 释放响应pending
    dbus_pending_call_unref(pending);  
    //读取参数
    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;  
} 

编译运行后server输出:

arg:Hello, D-Bus

客户端输出:

Got Reply: 1,2023

权限判断

在Dbus里通过DBusUserInfo里的UID来判断用户是root还是普通用户,普通用户无法访问系统总线只有root才有权限访问系统总线,这样做保证了两个通讯之间不会混淆和安全。

这个结构体里存储了应用进程的相关信息,当调用libdbus里连接相关的API时,libdbus内部都会去读取这个结构体里的值。

系统总线与服务总线

在Linux下有两个环境变量标识着系统总线与服务总线分别对应的dbus-daemon:

DBUS_SESSION_BUS_ADDRESS #服务总线
DBUS_SYSTEM_BUS_ADDRESS  #系统总线

系统总线只能用于处理系统事件,必须要以Root权限才能访问,主要提供给Linux桌面应用程序与系统服务之间的通讯,例如USB热拔插、网络等等,系统总线是唯一的为所有用户提供唯一的服务。

服务总线可以被普通应用程序访问使用,专门为普通应用程序提供服务的,它无论是发行版还是嵌入式都可以有多个服务总线,每一个用户对应一个服务总线。

当调用libdbus时如果当前uid是root用户则读取DBUS_SYSTEM_BUS_ADDRESS变量来确定dbus-daemon是哪一个,如果不存在则使用DBUS_STARTER_ADDRESS如果也不存在则使用DBUS_SESSION_BUS_ADDRESS,系统总线与服务总线使用同一个dbus-daemon不会导致权限的问题,因为每次做通讯时都会将uid传递过去,dbus-daemon内部会做区分。

使用系统总线

如果想要使用系统总线,则在Dbus里使用DBUS_BUS_SYSTEM

但是想使用DBUS_BUS_SYSTEM有许多限制,你需要在/etc/dbus-1/system.d/里定义你的配置文件,格式如下:

具体配置可以参考: dbus.freedesktop.org/doc/dbus-daemon
<busconfig>
        //定义只有root用户有权限使用my.test.pro这个服务
        <policy user="root">
                <allow own="my.test.pro"/>
                <allow send_destination="my.test.pro"/>
        </policy>
        //允许任何人调用my.test.pro服务上的方法
        <policy context="default">
                <allow own="my.test.pro"/>
                <allow send_destination="my.test.pro"/>
        </policy>
</busconfig>

如果你没有定义这项规则,那么运行时会报错:

Name ErrorConnection ":1.317" is not allowed to own the service "my.test.pro" due to security policies in the configuration file

这个原因是因为Dbus认为系统总线与服务总线应是不同的,系统总线应是更高级更安全的调用,需要明确它的规范和限制。

配置好了以后在启动server之后(注意以root身份)可以通过dbus_send来查看系统总线:

$:dbus-send --system \
--dest=org.freedesktop.DBus \
--type=method_call \
--print-reply \
/org/freedesktop/DBus \
org.freedesktop.DBus.ListNames

输出如下:

method return time=1679972259.563003 sender=org.freedesktop.DBus -> destination=:1.319 serial=3 reply_serial=2                                               
   array [                                                                                                                                                         string "org.freedesktop.DBus"                                                                                                                                string ":1.7"                                                                                                                                                string "org.freedesktop.timesync1"                                                                                                                     
      string ":1.8"                                                                                                                                                string ":1.9"                                                                                                                                          
      string "org.freedesktop.systemd1"                                                                                                                            string "org.freedesktop.ModemManager1"                                                                                                                 
      string "org.freedesktop.NetworkManager"                                                                                                                      string "org.freedesktop.oom1"                                                                                                                          
      string "net.hadess.PowerProfiles"                                                                                                                            string "org.freedesktop.resolve1"                                                                                                                            string "org.freedesktop.RealtimeKit1"                                                                                                                        string "org.freedesktop.Accounts"                                                                                                                      
      string "org.freedesktop.machine1" 
      string "my.test.pro"
      string ":1.62"
      string ":1.63"
      string ":1.42"
      string "org.freedesktop.PolicyKit1"
      string ":1.65"
      string ":1.43"
      string "org.bluez"
      string ":1.21"
      string ":1.44"
      string "org.freedesktop.PackageKit"
    ]

可以看到系统总线里my.test.pro已经在存在于系统总线列表里了

注意如果使用系统总线与服务总线两者之间不能进行通讯,是完全阻断的状态。
必须要以root身份才能使用DBUS_BUS_SYSTEM。
Logo

更多推荐