欢迎光临
我们一直在努力

【Linux】IPC通信:D-Bus通信机制

一、D-Bus通信机制的概述

1.1 什么是D-bus通信?

dbus通信和其他IPC方式的机制不同,这种通信方式是采用 逻辑总线 的方式进行的,这种逻辑总线如何理解呢?

逻辑总线其实就可以进行理解成消息转发中心,当两个不同的进程想要进行通信时,首先都必须先进行连接到dbus总线上,举个例子:当客户端进程想要进行增加音量的操作,首先客户端进程要和进程B都进行连接到dbus总线上,然后客户端进程将增加音量的消息进行发送到总线上,总线将这个消息进行转发给特定的进程(程序B)从而实现两个进程间的通信。

1.2 dbus 常见的两种通信方式

模式一:方法调用 (Method Call) —— “像打电话”

这是一对一的,有问必有答。

场景:你想让播放器暂停。

  • 步骤 1 (请求):你的控制程序(Client)向 D-Bus 发送消息:“呼叫播放器,执行暂停功能。”

  • 步骤 2 (转发):D-Bus 把指令传给播放器(Server)。

  • 步骤 3 (执行与回复):播放器暂停音乐,并原路返回一个消息:“OK,已暂停”。

模式二:信号 (Signal) —— “像发广播”

这是一对多的,只管发,不问谁收到了。

场景:执行切歌逻辑。

  • 步骤 1 (广播):电池管理程序发现电量剩 10%,它不需要知道谁关心这个事,它直接向 D-Bus 大喊一声(发送信号):“电量低!只剩10%了!”

  • 步骤 2 (分发):D-Bus 看了看手里的小本本,发现“屏幕亮度程序”和“弹窗通知程序”都订阅了这个话题。

  • 步骤 3 (响应):D-Bus 把消息推给这两个程序。屏幕变暗了,通知栏弹出了警告。电池程序发完就不管了。

二、D-Bus通信机制的原理

序列化的过程

三、不同进程间通信机制的汇总和选择

3.1 不同进程间通信机制的特点总结

通信方式 生活中的比喻 核心特点 优点 (爽在哪里) 缺点 (坑在哪里)
管道 (Pipe) 单向传声筒 / 水管 只能一个头进,一个头出。水流过去了就没了。

简单粗暴。

 

适合父子(父进程生子进程)之间顺手传点数据。

单向的(要想双向得拉两根管子);

 

且通常只能在有亲缘关系的进程间用。

命名管道 (FIFO) 公共的水龙头 即使两个人不认识(无亲缘关系),只要知道水龙头在哪(文件名),就能接水。

能跨越亲缘关系。

 

比普通管道灵活,硬盘上有一个文件做标记。

依然是流式数据,不知道哪里是头哪里是尾,读写效率一般。
消息队列 (Message Queue) 公司的信箱 / 快递柜 我把包写好名字扔进去,你什么时候有空什么时候来拿。

有格式(包裹知道大小);

 

异步(不用面对面等着);

 

数据能短暂存着。

哪怕发个小纸条也要打包,开销稍微有点大;

 

数据量太大时存取慢。

共享内存 (Shared Memory) 会议室的大白板 大家都能看见同一块区域,想写什么写什么,不用传来传去。

最快! 速度之王。

 

因为数据不需要复制来复制去,大家直接看白板。

危险!

 

如果两个人同时去写,字就乱了。必须配合“信号量”做红绿灯规则。

信号量 (Semaphore) 厕所的钥匙 / 红绿灯 它不怎么传数据,它主要是用来维持秩序的计数器。

控制节奏。

 

专门解决“共享内存”大家一窝蜂抢着写的问题。

不传数据。

 

它只负责说“红灯停绿灯行”,不能用来聊天。

信号 (Signal) 拍肩膀 / 警报器 突然拍你一下,或者火警响了。不管你在干啥,必须立刻停下来处理。

紧急且即时。

 

不需要建立连接,直接发给对应进程 ID。

信息量极少。

 

只能告诉你是“几号警报”,传不了具体内容。

套接字 (Socket) 打电话 / 微信 不仅能跟同一个办公室(本机)的人聊,还能跟隔壁大楼(别的电脑)的人聊。

最全能。

 

支持跨网络通信;

 

标准统一,哪里都能用。

稍微复杂。

 

建立连接的步骤多(握手、绑定IP端口等),速度比共享内存慢。

D-Bus (桌面总线) 全能管家 / 路由器 它是上面这些的高级封装(底层通常基于 Socket),专门管家里的家电。

智能化。

 

带自动启动、带权限管理、懂面向对象(方法/属性)。

比较重。

 

协议复杂,数据包大,不适合传海量数据(比如传高清视频流)。

3.2 不同通信机制在不同场景下的选择

四、libdbus 核心工作模型与 API 速查

通过上面三部分的了解,从上帝视角看到了dbus的通信是如何进行的,这个小模块从更细致的角度来看libdbus是如何进行实现这种通信机制的。

libdbus实现的功能十分强大齐全,这里只进行取最常用的20%来进行梳理和总结,这些是完全能够满足日常需求的。

4.1 三大核心对象

了解完宏观的dbus的流程,一个自然的问题就浮了上来:在代码层面我们如何进行操作这些流程?想要进行使用代码来进行操作这个流程,需要进行理解三个对象的概念,这三个对象是libdbus的核心。

  • DBusConnection:代表“管道”,所有消息都需要通过“管道”这个介质进行接收和发送。
  • DBusMessage:代表“包裹”,其中包含消息头(发给谁、使用什么方法)以及消息体(消息的实际内容)。
  • DBusMessageIter:代表“机械臂”,将要进行发送的内容进行发到介质中进行发送或者将内容进行从介质中拿出来,因为C/C++中没有反射机制所以说需要这个机械臂。

4.2 模块化API指南

理解了DBusConnection、DBusMessage、DBusMessageIter 这三个核心对象,我们就掌握了libdbus世界的“基本零件”。接下来,我们要看看如何用具体的“工具”——也就是 API函数——将这些零件组装起来完成进程间的通信。

4.2.1 连接和总线管理

API 函数名 作用 常见用法/注意
dbus_bus_get 连接到 D-Bus 守护进程 DBUS_BUS_SESSION (用户会话) 或 DBUS_BUS_SYSTEM (系统)。
dbus_bus_request_name 在总线上申请一个众所周知的名字 比如申请 com.example.WeakNet。只有拥有名字,别人才能找到你。
dbus_connection_register_object_path 核心函数:注册对象路径 将一个 C++ 类绑定到一个路径(如 /com/example/Object)。它需要一个 VTable(回调函数表),当总线收到消息时,会调用表里的函数。
dbus_connection_read_write_dispatch 驱动主循环 如果没有集成 GLib/Qt,你需要在一个 while 循环里不断调用它来处理收发消息。
dbus_connection_flush 强制发送 确保缓冲区的数据真正写出去了。

各个连接和总线管理的 API 的函数原型

// 连接到总线守护进程
// type: DBUS_BUS_SESSION (会话总线) 或 DBUS_BUS_SYSTEM (系统总线)
// error: 用于接收错误信息
DBusConnection *dbus_bus_get(DBusBusType type, DBusError *error);

// 申请一个众所周知的服务名 (例如 "com.example.MyService")
// flags: 通常用 DBUS_NAME_FLAG_REPLACE_EXISTING (抢占模式)
// 返回值: 比如 DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 表示成功
int dbus_bus_request_name(DBusConnection *connection,
const char *name,
unsigned int flags,
DBusError *error);

// 注册对象路径 (将 C 回调函数绑定到 D-Bus 路径)
// path: 对象路径,如 "/com/example/Object"
// vtable: 包含回调函数的结构体指针
// user_data: 传递给回调函数的上下文指针 (通常是 this 指针)
dbus_bool_t dbus_connection_register_object_path(DBusConnection *connection,
const char *path,
const DBusObjectPathVTable *vtable,
void *user_data);

// 断开连接并释放引用 (通常在程序退出时调用)
void dbus_connection_unref(DBusConnection *connection);

// 强制将发送缓冲区的数据写出 (确保消息发出去)
void dbus_connection_flush(DBusConnection *connection);

对于各个API的理解

  • dbus_bus_get

连接到系统总线(system bus)或者会话总线(session bus)

        功能理解

本来的程序是一个孤岛,现在程序接入了会话总线中,这里有两个选项DBUS_BUS_SESSION (用户私有,传桌面通知、控制播放器)和 DBUS_BUS_SYSTEM (系统级,传WIFI状态、硬件插拔)。

连接到系统总线这是共享的,即使这个程序调用了10次这个函数,拿到的其实是同一个Connection对象。

        参数理解

type:

  •  DBUS_TYPE_SESSION: 用于当前用户之间的应用间通信
  •  DBUS_TYPE_SYSTEM: 用于和系统守护进程通信

&err: 错误回执,如果D-Bus守护进程没有运行,或者没有执行权限这里就会报错。

        底层原理

Unix Domain Socket:底层连接到 /run/user/1000/bus(session)或 /var/run/dbus/system_bus_socket(system)

共享连接:

libdbus 内部有一个哈希表,当二次调用dbus_bus_get时,他发现已经连接过了,直接把旧的指针给你,然后引用计数+1

        内存机制

生命周期:这个conn对象常伴整个程序的生命周期,因为这个连接是共享的,如果出现在一个模块里关闭了连接,程序里的其他DBus模块也会直接进行断网,所以退出时要使用 dbus_connection_unref(conn) 减少引用计数。

  • dbus_bus_request_name:

在总线上抢注一个众所周知的名字

        功能理解

这个API做的工作就好像DNS域名注册,可以进行类比理解:

如果没有使用它,相当于别人只能通过IP地址进行找到你,但是每次重启IP都会发生变化;

如果有了它,相当于告诉总线守护进程:以后访问XXX服务名全部进行导向我。

        参数理解

conn: 进程和总线的电话线

name: 注册的字符串,必须是反向域名,例如 com.project.server

flag: 两个常用的选项:

  • DBUS_NAME_FLAG_REPLACE_EXISTING: 最常用,当现在的服务器死机或者卡住,允许我替换它成为新的主人。
  • DBUS_NAME_DO_NOT_QUEUE: 如果被占用了,我直接报错,不进行排队等待。

&err: 错误回执

        底层原理

这个函数其实进行发送了一条特殊的 RPC 消息给总线守护进程,然后总线守护进程给总线上刚刚进行注册XXX系统服务进行发送一个RequestName 的方法调用,这是一个同步阻塞的调用,直到守护进程进行回复注册成功或者失败

  • dbus_connection_register_object_path

设置路由规则,将发往特定路径的C消息进行导向特定的C函数

        功能理解

当在公司进行找人时,在前台进行登记,找 Object Path 的,都进行转接给 vtable 里定义的接线员(回调函数)处理。

        参数理解

path: 触发回调函数的路径,必须以/开头

&vtable: 这是一个虚函数表,他是一个结构体,里面存的是函数指针,.message_function 这是真正干活的函数

user_data: 携带上下文,因为回调函数是C静态函数,访问不到C++的静态指针,如果需要上下文则在这里进行传递this指针,如果不需要则进行传NULL。

        底层原理

libdbus 内部维护了一颗前缀树亦或是哈希表,当收到消息时,会进行解析消息头中的path 字段,在表中或者树中查找有没有注册过的节点,如果有就调用对应的函数指针

4.2.2 消息构建

API 函数名 对应消息类型 场景描述
dbus_message_new_method_call Method Call 客户端发起请求:“请帮我算一下 1+1”。
dbus_message_new_method_return Method Return 服务端回复结果:“结果是 2”。
dbus_message_new_error Error 服务端报错:“参数错误,无法计算”。
dbus_message_new_signal Signal 广播通知:“电量变低了!”
dbus_message_is_method_call (检查工具) 用来判断收到的消息是不是我要处理的方法。

各个消息构建的 API 函数原型

// 创建一个方法调用消息 (Client 端常用)
// destination: 目标服务名
// path: 目标对象路径
// interface: 目标接口名
// method: 方法名
DBusMessage *dbus_message_new_method_call(const char *destination,
const char *path,
const char *interface,
const char *method);

// 创建一个信号消息 (Server 端常用)
DBusMessage *dbus_message_new_signal(const char *path,
const char *interface,
const char *name);

// 创建一个方法返回消息 (Server 端回复 Client 用)
// method_call: 收到的那个请求消息对象
DBusMessage *dbus_message_new_method_return(DBusMessage *method_call);

// 创建一个错误消息 (Server 端报错用)
DBusMessage *dbus_message_new_error(DBusMessage *reply_to,
const char *error_name,
const char *error_message);

// 销毁/释放消息对象 (必须调用,否则内存泄漏)
void dbus_message_unref(DBusMessage *message);

对于各个API的理解

  • dbus_message_new_method_call

通常由客户端发起,创建的一个方法调用的消息

        功能理解

通常由客户端发起的一个命令包,明确指定了找谁(服务),找那个部件(路径),用什么技能(接口),执行什么动作(方法),并等待对方说:好的,办完了

        参数解析

destination:目标服务名,像 "com.example.MusicService"

path:对象路径,像"/com/example/Music"

interface:接口名,像"com.example.Player"

method:方法名,像"Play"

        底层原理

消息类型:他将消息头中的字段 Type 进行设置成 DBUS_MESSAGE_TYPE_METHOD_CALL 

序列号:自动生成一个唯一的 Serial Number (序列号),用于后续进行匹配回复

        内存机制

返回值为一个指向新消息的指针,使用完后必须进行调用 dbus_message_unref(msg),否则会进行内存泄漏。

  • dbus_message_new_signal

创建一个广播信号消息

        功能理解

没有收件人的消息,相当于大喇叭,谁想听谁听

        参数理解

path:广播的发射源

interface:属于哪个接口频道

name:信号名称叫什么

        底层原理

广播消息的类型是 DBUS_MESSAGE_TYPE_SIGNAL ,这个消息首先会进行发送给守护进程,守护进程进行见擦汗谁注册了 Macth Rule (匹配规则),然后把这个消息进行复制发给所有感兴趣的进程。

  • dbus_message_is_method_call

判断收到的消息是不是我要进行处理的方法请求

        功能理解

当我收到一个包裹(msg),这里由于是广播收到的,收到的包裹不一定是发送给我的,需要通过InterFace → 寄给谁的?  Method → 要干什么?只有这两个字段全对上了,才进行读取包裹内容。

        参数解析

msg: 收到的消息

interface: 期望的接口名

method: 期望的函数名

        底层原理

字符串比较:相当于高级的strcmp操作,直接进行读取头中的INTERFACE和METHOD这两个字段进行比较。

  • dbus_message_new_method_return

通常是由服务端进行发器创建一个方法回复消息,回复之前客户端发起的请求

        功能理解

收到并回复客户端发过来的请求,由于是进行回复,系统自动会进行填写好收件人(即原来的发件人),系统自动填好 Re:主题(关联序列号),因此不需要关心对方是谁,只管往里面进行塞结果即可。

        参数理解

DBusMessage *reply = dbus_message_new_method_return(call_msg);

call_msg就是刚才客户端进行发送的构建消息,服务端进行接受的那个构建消息。

        底层原理

自动路由:它会从 call_msg 中进行读取发送者ID(如:1.45),设为 reply 的目标。

配对关键:它会读取 call_msg 中的 Serial 字段进行填入 reply 中的 Reply Serial 字段,这样客户端就能知道这是我刚才的那个Play的回复了

        内存机制

一定要记得进行dbus_message_unref(reply).

4.2.3 迭代器

  • 向消息中进行写入数据
API 函数名 作用 你的代码示例
dbus_message_iter_init_append 初始化写入迭代器 准备开始往 msg 里写参数。
dbus_message_iter_append_basic 写入基础类型 写入 int, string, bool 等。注意:字符串必须传 const char** 的地址!
dbus_message_iter_open_container 打开容器 开始写数组 (ARRAY) 或字典 (DICT_ENTRY)。
dbus_message_iter_close_container 关闭容器

数组写完了,必须关闭,否则消息格式错误。

dbus_message_append_args 一键打包 代码短,不支持数组/字典

// 初始化一个用于"写入"的迭代器 (绑定到 msg)
void dbus_message_iter_init_append(DBusMessage *message,
DBusMessageIter *iter);

// 写入基础类型数据 (int, string, bool 等)
// type: DBUS_TYPE_STRING, DBUS_TYPE_INT32 等
// value: 变量的地址 (注意:如果是字符串,传 char**; 基础类型传 &var)
dbus_bool_t dbus_message_iter_append_basic(DBusMessageIter *iter,
int type,
const void *value);

// 打开一个容器 (数组或字典) 开始写入
// type: DBUS_TYPE_ARRAY 或 DBUS_TYPE_DICT_ENTRY
// contained_signature: 容器内元素的类型签名 (如字符串数组是 "s")
// sub_iter: 用于操作容器内部的新迭代器
dbus_bool_t dbus_message_iter_open_container(DBusMessageIter *iter,
int type,
const char *contained_signature,
DBusMessageIter *sub_iter);

// 关闭容器 (写完数组后必须调用)
dbus_bool_t dbus_message_iter_close_container(DBusMessageIter *iter,
DBusMessageIter *sub_iter);

// (便利函数) 一次性追加多个简单参数,以 DBUS_TYPE_INVALID 结尾
dbus_bool_t dbus_message_append_args(DBusMessage *message,
int first_arg_type,
…);

对于各个API的理解

  • dbus_message_iter_init_append

初始化写入迭代器,准备往消息中进行写入数据

        功能理解

类比理解:拿到一个空箱子(message),将传送带(iter)进行架到箱子口,准备往里面进行放东西。

        参数理解

message:目标消息

iter:这是栈上进行分配的结构体,调用后结构体会进行初始化,内部的指针进行指向 Message 数据区的末尾。

        底层原理

追加模式:DBus 的消息构建是只能进行追加的,不能跳回去进行修改前面写的数据,这个函数就是将指针进行定位到缓冲区的最后部分。

  • dbus_message_iter_append_basic

往消息里进行写入一个基础的数据类型例如 int,string,bool 这种

        功能理解

类比理解:传送带(iter)正在转动,现在只需要将一块砖头(比如int)或者一张纸条(例如string)放到传动带上,传送带会自动把东西进行送进箱子,并进行前进一格。

        参数理解

iter: 迭代器,也就是我们上面进行类比的传动带

type: 想要进行放到消息(箱子中)的类型,包括 DBUS_TYPE_INT、DBUS_TYPE_STRING 等等。

value: 消息的实际内容。

        底层原理

内存对齐:首先会自动进行补充空数据进行内存对齐。

字节序处理:然后按照本机的字节序(大端或者小端)进行写入。

深拷贝:将数据进行memcp到消息的内部缓冲区。

  • dbus_message_iter_open_container

开始写入复杂类型(如数组 array)

        功能理解

当想要进行寄一箱散装的糖果(例如字符串数组),不能直接散着放,需要进行在主箱子中在进行创建一个透明收纳盒,用于进行存放散装的数据,这个函数就是做这个工作的。

        参数解析

iter:父迭代器,指向的是外面的那个大箱子

type:类型,一般是DBUS_TYPE_ARRAY

contained_signature:数组里面的类型

sub_iter:子迭代器,指向的是收纳盒内部

        底层原理

 当你进行 open_container 时,D-Bus头部会进行预留4个字节用来存储数组长度,因为此时还不知道数据的实际长度,所以先填0,后续在进行填入这4个字节

  • dbus_message_iter_close_container

封箱,结束一个数组或者字典的写入

        功能理解

刚刚通过 dbus_message_iter_open_container 进行打开的小盒子,里面装了很多东西,这个dbus_message_iter_close_container 就是把箱子的盖子给装上,并进行统计一下刚才装了多少东西(主要就是进行计算长度),然后把这个数字进行写到盒子的盖子上面。

        参数解析

iter:父迭代器,这个迭代器相当于打开箱子的那只手

sub_iter:子迭代器,相当于在箱子里干活的那只手

        底层原理

 当你进行 open_container 时,D-Bus头部会进行预留4个字节用来存储数组长度,但当时是0;当进行写入数据时,sub_iter 一直往后走;当调用close_container 时,DBus会进行计算当前位置和open时位置的差值(即数据长度),把这个长度进行填进那4个预留字节中。

        内存机制

这个函数是执行流中必须要进行调用的,否则头部的长度是0或者是垃圾值,当接收方解析时,会认为这是一个空数组,或者读取越界导致崩溃

  • dbus_message_append_args(简易)

往消息中进行一次性写入多个简单的参数(打包)

        功能理解

按照我们之前的类比,就是将所有的零件进行放到传送带上,机器自动将他们塞进箱子里。

        参数解析

采用参数列表的形式,类型+数据,最后以 DBUS_TYPE_INVALID 结尾。

        底层原理

将数据进行复制一份存入消息内存。

  • 从消息中进行读取数据
API 函数名 作用 你的代码示例
dbus_message_iter_init 初始化读取迭代器 准备读取 msg 的参数。
dbus_message_iter_get_arg_type 查看当前数据类型 检查当前是不是 DBUS_TYPE_STRING。
dbus_message_iter_get_basic 读取基础类型的值 把数据读到你的 C 变量里。
dbus_message_iter_next 移动到下一个参数 指针后移,准备读下一个。
dbus_message_iter_recurse 进入容器 如果当前是数组,用这个函数进入数组内部遍历。
dbus_message_get_args 一件拆包 代码短,不支持数组/字典

// 初始化一个用于"读取"的迭代器
// 返回值:如果有参数返回 TRUE,无参数返回 FALSE
dbus_bool_t dbus_message_iter_init(DBusMessage *message,
DBusMessageIter *iter);

// 获取当前迭代器指向的数据类型
// 返回值: DBUS_TYPE_STRING, DBUS_TYPE_INT32 等,或 DBUS_TYPE_INVALID (读完了)
int dbus_message_iter_get_arg_type(DBusMessageIter *iter);

// 获取当前参数的值
// value: 接收数据的变量指针
void dbus_message_iter_get_basic(DBusMessageIter *iter,
void *value);

// 迭代器移动到下一个参数
dbus_bool_t dbus_message_iter_next(DBusMessageIter *iter);

// 进入容器内部 (如果当前类型是 ARRAY 或 DICT,用这个进入内部读取)
// sub_iter: 初始化这个子迭代器来读取容器内容
void dbus_message_iter_recurse(DBusMessageIter *iter,
DBusMessageIter *sub_iter);

// (便利函数) 一次性读取多个简单参数
dbus_bool_t dbus_message_get_args(DBusMessage *message,
DBusError *error,
int first_arg_type,
…);

对于各个API的理解

  • dbus_message_get_args

从消息中一次性读取多个简单的数据包(解包操作)

        功能理解

当收到包裹,告诉机器帮我进行按照我的传参的类型进行解析。

        参数解析

通过类型+值的方式进行解析

        底层原理

便利封装:它内部进行封装了 dbus_message_iter_init 和 dbus_message_iter_next

  • dbus_message_iter_recurse

进入容器,准备进行读取容器的数据

        功能理解

当前的主迭代器正指着一个密封的箱子(数组、结构体或者字典),不能直接通过 get_basic 去吃这个箱子本身,这个函数的作用就是穿透箱子的外壳,初始化一个新的迭代器,并把这个新的迭代器精准的指向箱子内部的第一个元素上。

        参数理解

iter: 父迭代器,指向的是箱子的外壳,在调用的时候它保持不动,仅仅作为路标。

sub_iter: 子迭代器,这是被初始化后真正伸进箱子里的那只手,用来从第一个元素开始逐个遍历内部数据。

        底层原理

dbus消息在内存中是一块连续的二进制数据,iter 此时停留在容器的头部元数据区;当调用此函数时,libdbus 会根据当前容器的类型,计算出头部占用了多少字节,然后进行指针偏移计算;他将 sub_iter 的内部指针进行指向跳过头部后的数据区的其实地址,这个操作是只读的计算,父迭代器的位置不会发生变化。

        内存拷贝

零拷贝:不需要及逆行重新生成新的堆内存,也不会进行复制新的数据,只是在现有的消息缓冲区上创建一个新的游标。

强制规则:进入容器内的这个函数是进行读取复杂类型,像 ARRAY、STRUCT、DICT_ENTER、VARIANT 的唯一入口。如果不经过这一步直接进行读取的话,程序会因为类型不匹配而读取失败或者读取到垃圾数据。

4.2.4 发送与接收

API 函数名 作用 区别
dbus_connection_send 异步发送 发送后立即返回,不等待回复。信号(Signal) 和 返回值(Return) 通常用这个。
dbus_connection_read_write_dispatch 收+发+回调
dbus_connection_send_with_reply 发送并获取句柄 用于方法调用(Call)。它不会阻塞,但会给你一个 DBusPendingCall* 句柄,你可以稍后检查有没有回复。
dbus_connection_send_with_reply_and_block 同步阻塞发送 发送并死等回复。最简单但会卡住界面,慎用。
dubs_bus_add_match 订阅信号

// 异步发送
// 发送后立即返回,不等待回复。信号(Signal) 和 返回值(Return) 通常用这个。
// serial: (可选) 返回消息序列号,如果不需要可传 NULL
dbus_bool_t dbus_connection_send(DBusConnection *connection,
DBusMessage *message,
dbus_uint32_t *serial);

// 收+发+回调 (驱动主循环)
// 负责从 socket 读取数据、发送队列数据以及分发消息到回调函数
// timeout_milliseconds: 超时时间,-1 为阻塞等待,0 为非阻塞
dbus_bool_t dbus_connection_read_write_dispatch(DBusConnection *connection,
int timeout_milliseconds);

// 发送并获取句柄 (用于方法调用 Call)
// 它不会阻塞,但会给你一个 DBusPendingCall* 句柄,你可以稍后检查有没有回复。
// pending_return: 接收句柄的指针
dbus_bool_t dbus_connection_send_with_reply(DBusConnection *connection,
DBusMessage *message,
DBusPendingCall **pending_return,
int timeout_milliseconds);

// 同步阻塞发送
// 发送并死等回复。最简单但会卡住界面,慎用。
// 返回值: 收到的回复消息 (需要手动 unref)
DBusMessage *dbus_connection_send_with_reply_and_block(DBusConnection *connection,
DBusMessage *message,
int timeout_milliseconds,
DBusError *error);

// 订阅信号
// 添加匹配规则,告诉总线你想接收哪些信号
// rule: 匹配规则字符串,例如 "type='signal',interface='com.example.Test'"
void dbus_bus_add_match(DBusConnection *connection,
const char *rule,
DBusError *error);

  • dbus_connection_send

将消息进行放到发送队列,这个过程是非阻塞的。

        功能理解

相当于把dbus_message_iter_close_container 进行封好的箱子进行放到门口并进行拍了一张照片,等待快递员来取

        参数理解

        底层原理

队列持有:libdbus会把message加入内部的Outgoing Queue链表中

引用增加:为了防止你把msg销毁导致发送失败,库会自动对message执行ref()进行引用计数+1

        内存机制

调用 send 后,必须调用 dbus_message_unref(message)进行引用计数-1,否则之前保障msg销毁导致的引用计数+1将会造成内存泄漏

  • dbus_connection_read_write_dispatch

驱动主循环,处理收发

        功能理解

这个函数功能相当于一个管理员,将dbus_connection_send 进行将打包好的放到门口的包括进行发货处理,这才是真正将包裹进行放到dbus总线上。

这个管理员非常懒惰,需要手动驱动他干活,只有进行手动驱动了他才会进行执行一套标准动作:

发货:看向门口待发送的包裹,把包裹进行交给物流货车(socket)

收货:从物流货车(socket)上卸下新的包裹,这相当将网络中的二进制数据进行放到内存缓冲区。

分发:通过包裹上的信息(/com/example/music)进行分发(触发回调)

        参数理解

dbus_connection_read_write_dispatch(conn, 0);

0 表示非阻塞

        底层原理

I/O 复用:内部调用了 poll() 或 select() 系统调用来检查 Socket 状态。

        内存机制

无特殊内存操作。但如果长时间不调用,接收缓冲区满,发送队列堆积,可能导致内存飙升或连接被断开。

  • dbus_connection_send_with_reply_and_block

同步进行发送消息并阻塞当前线程直到收到回复或超时

        功能理解

通过调用该函数将消息进行发送给总线,然后在原地进行等待直到对方把结果发回来,或者等待的时间超过设定的时限,这和普通的send发送操作完全不同

        参数解析

connection: 连接句柄,这就是连接到dbus总线上的线

message: 想要进行发送的请求消息

timeout_milliseconds: 最大超时时间(单位是毫秒),如果传-1默认是25秒,还有一个参数是 DBUS_TIMEOUT_INFINITE 无限等待,如果对象死机,自己这一方也就寄了。

error: 错误接收器,如果发送失败或者超时,会将原因进行写到这里

        底层原理

发送与标记:函数内部会将这个消息进行放入发送队列,强制刷新(flush)到底层的 Socket,并记录下这条消息的序列号(比如#1)

建立临时监听:他会在内部进行创建一个临时的 DBusPendingCall 对象

私有循环:函数内部会进入一个while循环,内部做了以下工作:① 读取socket上的新数据 ②检查收到的消息是否是针对 #1 的回复,如果是

        内存机制

成功时返回一个 指向新消息的指针,失败时返回NULL。  

4.2.5 内存与错误管理

API 函数名 规则 严重程度
dbus_message_unref 销毁消息。每当你 new 一个消息,或者收到一个消息处理完后,必须调用它。 非常严重 (漏写=内存泄漏,多写=崩溃)
dbus_error_init 初始化错误对象。使用前必须调用。 严重
dbus_error_free 释放错误对象内部的字符串。 严重
dbus_connection_unref 断开连接并释放内存。 严重

// 异步发送消息 (不阻塞,放入队列即返回)
// serial: (可选) 返回消息的序列号,传 NULL 即可
dbus_bool_t dbus_connection_send(DBusConnection *connection,
DBusMessage *message,
dbus_uint32_t *serial);

// 同步发送并阻塞等待回复 (慎用,会卡住线程)
// timeout_milliseconds: 超时时间,-1 表示默认
DBusMessage *dbus_connection_send_with_reply_and_block(DBusConnection *connection,
DBusMessage *message,
int timeout_milliseconds,
DBusError *error);

// 驱动主循环 (读取、写入、分发消息)
// 只有在没用 GLib/Qt 等外部主循环时才需要手动循环调用它
dbus_bool_t dbus_connection_read_write_dispatch(DBusConnection *connection,
int timeout_milliseconds);

// 从接收队列中取出一个消息 (如果没有消息返回 NULL)
DBusMessage *dbus_connection_pop_message(DBusConnection *connection);

// 添加匹配规则 (用于接收信号 Signal)
// rule: 如 "type='signal',interface='com.example.Interface'"
void dbus_bus_add_match(DBusConnection *connection,
const char *rule,
DBusError *error);

五、使用dbus通信写一个demo

实战是加强技术理解的一个非常好的一个途径,通过下面的这个demo能够增强对dbus的使用和理解。

首先先来说一下这个demo的功能:

通过dbus进行创建了一个简单音乐播放系统,包含一个服务端、一个客户端,服务器是音乐播放器本身,提供播放功能,客户端相当于遥控器用来进行控制播放器。

5.1 通信的流程图

┌─────────────┐ 1.连接D-Bus ┌─────────────┐
│ 客户端 │ ◀─────────────────▶ │ D-Bus系统 │
│ (遥控器) │ │ (电话局) │
└─────────────┘ └─────────────┘
│ │
│ 2.发送"播放歌曲"请求 │
├────────────────────────────────────▶│
│ │ 3.转发请求
│ ├─────────────────▶
│ │ │
│ │ ┌─────────────┐
│ │ │ 服务端 │
│ │ │ (播放器) │
│ │ └─────────────┘
│ │ │
│ 4.返回"开始播放"回复 │ 5.处理请求
│◀───────────────────────────────────┤◀────────────────┤
│ │
│ │ 6.发送音量变化广播
│ │◀────────────────┤
│ 7.接收音量变化信号 │
│◀───────────────────────────────────┤

5.2 服务端代码

#include<iostream>
#include<dbus/dbus.h>
#include<unistd.h>

const char* const BUS_NAME="com.example.musicPlayerService";
const char* const INTERFACE="com.example.musicPlayer";
const char* const OBJECT_PATH="/com/example/musicPlayer";
const char *playlist[] = {"Song A – Intro", "Song B – MainTheme", "Song C – Ending"}; //模拟播放列表的数据
DBusHandlerResult message_handler(DBusConnection* conn,DBusMessage* msg,void*user_data)
{
DBusMessage* reply=NULL;
DBusMessageIter args;

//处理简单的方法调用play(string)
if(dbus_message_is_method_call(msg,INTERFACE,"Play"))
{
char* song_name;
DBusError err;
dbus_error_init(&err);

//读取简单的参数
if(!dbus_message_get_args(msg,&err,DBUS_TYPE_STRING,&song_name,DBUS_TYPE_INVALID))
{
std::cerr<<"参数错误:"<<err.message<<std::endl;
dbus_error_free(&err);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
std::cout<<"[server] 收到play请求:"<<song_name<<std::endl;

//创建回复的消息
reply=dbus_message_new_method_return(msg);
const char* ret_str="Playing Started";
dbus_message_append_args(reply,DBUS_TYPE_STRING,&ret_str,DBUS_TYPE_INVALID);
}

//处理复杂返回值调用GetPlayList → Array of Strings
else if(dbus_message_is_method_call(msg,INTERFACE,"GetPlayList"))
{
std::cout<<"[server] 收到GetPlayList请求"<<std::endl;
reply=dbus_message_new_method_return(msg);

//迭代器操作:写入容器
dbus_message_iter_init_append(reply,&args);
DBusMessageIter sub_iter;
dbus_message_iter_open_container(&args,DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING,&sub_iter);
for(int i=0;i<3;i++)
{
const char* song=playlist[i];
dbus_message_iter_append_basic(&sub_iter,DBUS_TYPE_STRING,&song);
}
//关闭容器
dbus_message_iter_close_container(&args,&sub_iter);
}

//发送回复
if(reply)
{
//发送消息
dbus_connection_send(conn,reply,NULL);
//释放内存
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
//VTable定义
const DBusObjectPathVTable vtable={
.unregister_function = NULL,
.message_function = message_handler
};

int main()
{
DBusConnection* conn;
DBusError err;
//1、连接到dbus守护进程C
dbus_error_init(&err);
conn=dbus_bus_get(DBUS_BUS_SESSION,&err);
if(dbus_error_is_set(&err))
{
std::cerr<<"Connection error:"<<err.message<<std::endl;
return 1;
}

//2、申请 dbus name(成为服务)
int ret=dbus_bus_request_name(conn,BUS_NAME,DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
if(dbus_error_is_set(&err))
{
std::cerr<<"Request Name error:"<<err.message<<std::endl;
return 1;
}

//3、注册对象路径(绑定回调)
if(!dbus_connection_register_object_path(conn,OBJECT_PATH,&vtable,NULL))
{
std::cout<<"OOM handing object path"<<std::endl;
return 1;
}

std::cout<<"[server] 服务已启动"<<std::endl;

int volume=0;
while(1)
{
//驱动主循环
dbus_connection_read_write_dispatch(conn,0);
sleep(1);
volume=(volume+5)%100;

//创建信号
DBusMessage* sig=dbus_message_new_signal(OBJECT_PATH,INTERFACE,"VolumeChanged");
if(sig)
{
DBusMessageIter args;
dbus_message_iter_init_append(sig,&args);
dbus_message_iter_append_basic(&args,DBUS_TYPE_INT32,&volume);

std::cout<<"[server] 发送信号 Volume:"<<volume<<std::endl;
dbus_connection_send(conn,sig,NULL);
dbus_message_unref(sig);
}
}
return 0;
}

5.3 客户端代码

#include<iostream>
#include<dbus/dbus.h>
#include<unistd.h>

const char* const BUS_NAME="com.example.musicPlayerService";
const char* const INTERFACE="com.example.musicPlayer";
const char* const OBJECT_PATH="/com/example/musicPlayer";
//调用简单方法
void call_paly_method(DBusConnection* conn)
{
DBusMessage* msg;
DBusMessage* reply;
DBusError err;
dbus_error_init(&err);

const char* song="消愁";
//1.创建方法调用
msg=dbus_message_new_method_call(BUS_NAME,OBJECT_PATH,INTERFACE,"Play");
//2.一键打包消息
dbus_message_append_args(msg,DBUS_TYPE_STRING,&song,DBUS_TYPE_INVALID);
//3.同步发送并等待回复(阻塞)
reply=dbus_connection_send_with_reply_and_block(conn,msg,-1,&err);
dbus_message_unref(msg);
if(dbus_error_is_set(&err))
{
std::cerr<<"send_with_reply_and_block Err:"<<err.message<<std::endl;
dbus_error_free(&err);
return ;
}
//4.解析返回值
char* result_str;
if(dbus_message_get_args(reply,&err,DBUS_TYPE_STRING,&result_str,DBUS_TYPE_INVALID))
{
std::cout<<"[client] 服务端回复:"<<result_str<<std::endl;
}
dbus_message_unref(reply);
}

//调用复杂方法
void call_get_playList(DBusConnection* conn)
{
DBusMessage* msg;
DBusMessage* reply;
DBusError err;
dbus_error_init(&err);
//1.创建方法调用
msg=dbus_message_new_method_call(BUS_NAME,OBJECT_PATH,INTERFACE,"GetPlayList");
//2.发送并等待回复
reply=dbus_connection_send_with_reply_and_block(conn,msg,-1,&err);
dbus_message_unref(msg);
if(dbus_error_is_set(&err))
{
std::cerr<<"send_with_reply_and_block Err:"<<err.message<<std::endl;
return ;
}
//3.使用迭代器进行读取容器
DBusMessageIter iter;
DBusMessageIter sub_iter;
dbus_bool_t ret=dbus_message_iter_init(reply,&iter);
if(ret==0)
{
std::cerr<<"Iter init Err"<<std::endl;
}
if(dbus_message_iter_get_arg_type(&iter)==DBUS_TYPE_ARRAY)
{
dbus_message_iter_recurse(&iter,&sub_iter);
std::cout<<"[client] 播放列表:"<<std::endl;

//循环读取
while(dbus_message_iter_get_arg_type(&sub_iter)!=DBUS_TYPE_INVALID)
{
char* song_name;
dbus_message_iter_get_basic(&sub_iter,&song_name);
std::cout<<song_name<<std::endl;
dbus_message_iter_next(&sub_iter);
}
}
dbus_message_unref(reply);
}

int main()
{
DBusConnection* conn;
DBusError err;
dbus_error_init(&err);
//1.连接到会话总线
conn=dbus_bus_get(DBUS_BUS_SESSION,&err);
if(dbus_error_is_set(&err))
{
std::cout<<"Connection error:"<<err.message<<std::endl;
return 1;
}
std::cout<<"[client] 连接会话总线成功"<<std::endl;

//2.主动调用两个方法
call_paly_method(conn);
call_get_playList(conn);
//3.监听信号添加匹配规则
std::cout<<"[client] 开始监听VolumeChanged 信号"<<std::endl;
dbus_bus_add_match(conn,"type='signal',interface='com.example.musicPlayer'",&err);
if(dbus_error_is_set(&err))
{
std::cerr << "[client] 添加匹配规则错误: " << err.message << std::endl;
dbus_error_free(&err);
return 1;
}
//4.循环接收信号
std::cout<<"开始循环接收信号"<<std::endl;
while(1)
{
//驱动IO
dbus_connection_read_write(conn,0);

//尝试拿数据
DBusMessage* msg=dbus_connection_pop_message(conn);
if(msg==NULL)
{
usleep(100000);
continue;
}

//判断是不是信号
if(dbus_message_is_signal(msg,INTERFACE,"VolumeChanged"))
{
int vol;
DBusMessageIter iter;
dbus_bool_t ret=dbus_message_iter_init(msg,&iter);
if(ret==0)
{
std::cerr<<"dbus_message_iter_init Err"<<std::endl;
}
dbus_message_iter_get_basic(&iter,&vol);
std::cout<<"[client] 收到信号:音量变更为:"<<vol<<std::endl;
}
dbus_message_unref(msg);
}
return 0;
}

5.4 结果展示

赞(0)
未经允许不得转载:171主机测评 » 【Linux】IPC通信:D-Bus通信机制
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址