Linux Dbus
Linux Dbus是由项目开发的一款IPC进程间通讯技术,它是基于在Unix 域套接字实现的,它在sockets上面进行了封装并提供了一套更加规范、方便、安全的通讯机制,同时定义了一套标准接口,目前已经被大量Linux发行版所采用,比如Ubuntu、Centos...Dbus最初的目的就是为桌面应用程序之间提供IPC通讯技术它不属于Linux内核的一部分,它目前没有被Linux内核所采纳。
简介
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对象提供了四种类型:
signals 信号
method calls 方法调用
method returns 方法返回
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下一种通讯方式,它类似于广播,任何应用程序都可以监听或发送消息类型。
应用程序通过向总线注册感兴趣的信号事件类型,当总线收到对应信号类型时会转发给所有感兴趣的应用程序,发送的信号可以带有参数,应用程序可以通过过滤参数来决定那些信号是自己感兴趣的,同时应用程序收到信号不会有任何返回。
通讯过程
应用程序向Dbus注册感兴趣的信号对象
Dbus收到对应信号之后转发给所有感兴趣的应用程序
Method (方法)
基本概念
Method是Dbus下一种远程调用的方式,通过Method,Dbus运行一个应用程序去调用另外一个应用程序里的函数并支持传递参数以及返回值。
在对象类型里提到过两个对象类型:Method call和Method return,这两个类型分别对应调用与返回,call由调用者发起而return由执行者返回执行结果。
如果产生了异常错误,执行者也可以返回error对象告知调用者本次调用不合法或产生了异常错误。
调用过程
调用者向Dbus发送一次Method调用
Dbus将调用转发给执行者
执行者检查调用是否合法,如果不合法则应返回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
数据结构
消息总线相关数据块 | |
一个8字节的结构,可以在不支持int64的情况下访问int64 | |
数据库总线地址 | |
用于存储分配的数据 | |
如果需要调整数组长度;这些调整将由该结构描述 | |
原子结构,用于递减操作 | |
数据库总线身份验证 | |
客户端DBusAuth的“子类” | |
从命令名到枚举的映射 | |
表示特定身份验证机制的虚拟表 | |
客户端DBusAuth的“子类” | |
有关身份验证状态的信息 | |
实施细节 | |
一个简单的值联合,允许您访问字节,就好像它们是各种类型一样;通过void指针和varargs处理基本类型时很有用 | |
Dbus互斥锁 | |
总线条件 | |
总线连接 | |
总线计数器 | |
总线凭据 | |
用于存储连接上的应用程序数据 | |
总线数据分配器 | |
在给定元素中存储实际用户数据集的数据结构 | |
Internals of directory iterator | |
表示异常的对象 | |
用于表示空闲链表中的一个元素 | |
有关UNIX组的信息 | |
全球唯一的ID;每个DBusServer都有一个,每个安装了libdbus的机器也有一个 | |
总线哈希条目 | |
哈希迭代器对象 | |
总线哈希表 | |
消息头数据及其一些缓存的详细信息 | |
缓存的有关消息中标头字段的信息 | |
cookie文件中的单个密钥 | |
总线钥匙环 | |
链接列表中的节点 | |
DBusMemBlock对象表示单个malloc()返回的块,该块被分块为内存池中的对象 | |
内存池总线 | |
总线消息 | |
表示消息筛选函数的内部结构 | |
DBusMessageIter结构体;不包含公共字段 | |
dbus 1.10.0中堆栈上DBusMessageIter的布局 | |
总线消息加载器 | |
总线消息RealIter | |
内部使用,未公开 | |
总线对象路径V表 | |
表示单个已注册子树处理程序的结构,或是已注册子树处理器的父节点 | |
总线对象树 | |
总线挂起呼叫 | |
内部使用,未公开 | |
内部使用,未公开 | |
总线预分配 | |
总线实际错误 | |
总线真实哈希迭代器 | |
总线实际字符串 | |
内部使用,未公开 | |
总线服务器 | |
总线服务器套接字 | |
总线服务器路径V表 | |
SHA算法的结构存储状态 | |
总线签名迭代器 | |
总线签名迭代器 | |
套接字接口 | |
具有stat()结果的可移植结构 | |
内部使用,未公开 | |
必须实现的函数才能使D-Bus库线程感知 | |
总线超时 | |
总线超时列表 | |
总线传输 | |
总线传输套接字 | |
总线传输V表 | |
类型读取器是一个迭代器,用于从值块中读取值 | |
用于类型读取器的虚拟表 | |
类型写入器是一个迭代器,用于写入值块 | |
有关UNIX用户的信息 | |
一种不透明的数据结构,包含任何单个D总线消息项的序列化形式,其签名为单个完整类型 | |
总线监视器 | |
总线监视列表 | |
内部使用,未公开 | |
内部使用,未公开 | |
此结构表示关闭时要调用的函数 |
接口
由于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, ¶m);
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,¶m)){
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。
更多推荐
所有评论(0)