IPC之十二:使用libdbus在D-Bus上异步发送/接收信号的实例

IPC 是 Linux 编程中一个重要的概念,IPC 有多种方式,本 IPC 系列文章的前十篇介绍了几乎所有的常用的 IPC 方法,每种方法都给出了具体实例,前面的文章里介绍了 D-Bus 的基本概念以及调用远程方法的实例,本文介绍 D-Bus 的异步处理机制,以及信号处理的基本方法,本文给出了异步处理 D-Bus 的实例,附有完整的源代码;本文实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文不适合 Linux 编程的初学者阅读。

1 D-Bus 的信号(Signal)

  • 在阅读本文之前,建议阅读关于 D-Bus 的另一篇文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》

  • 在文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中,介绍了服务端如何在 D-Bus 上提供方法调用服务以及客户端如何向服务端请求一个方法调用服务;

  • 通过 D-Bus 向服务端请求方法调用服务,仅仅是 D-Bus 一半的功能,D-Bus 还支持异步的广播通信方法,这种机制称为信号(Signal),当服务端需要向大量接收者发送通知时,该机制非常有用;

  • 举例来说,如果系统正在关闭、网络连接中断以及类似的系统范围内的情况,相关系统服务进程应该广播一个通知,使对这些服务有需求的进程能够及时做出反应,这样一种方式,使得接收信号的进程无需轮询服务状态;

  • D-Bus 的信号(Signal)与调用方法(Method Call)有许多类似的地方,这里简要回顾一下在上一篇文章中讨论的调用方法的概念:

    1. 服务程序连接 D-Bus(dbus-daemon),获得一个连接,D-Bus 随机给这个连接分配一个唯一名称;
    2. 为该连接绑定一个固定的名称(Bus Name),以方便客户端访问这个连接,总线名称通常以反向域名的形式命名;
    3. 在该连接下可以建立一个或多个对象(Object),对象名称以路径(类似文件系统路径)表示;
    4. 在每个对象上可以建立一个或者多个接口(Interface),接口的名称也是使用反向域名的命名方式;
    5. 每个接口下可以有一个或者多个方法(Method);
    6. 客户端需要请求服务端的某个方法时,需要知道总线名称、对象路径、接口名称以及方法名称,并将调用参数传送给服务端;
  • 信号(Signal)也是建立在一个接口下,一个接口下不仅可以有一个或者多个方法,还可以有一个或者多个信号,

    • 1 – 4 同上;
    1. 每个接口下可以有一个或者多个信号(Signal),信号的命名与调用方法一样,没有很多规则,比如:sig_demo
    2. 客户端想要收到某个信号时,需要向总线注册,告知总线感兴趣的信号(包括:对象路径、接口名称和信号名称),只能收到向总线注册过的信号;
  • 所以其实一个接口下可以有若干个方法和信号,除此之外,接口下还可以有若干个属性(Properties),方法、信号、属性组合在一起构成一个接口;

  • 本文仅讨论接口,有关属性的事情,以后的文章中再讨论;

  • 发送信号通常是服务端的事情,信号通常是以广播的方式发出,而只有订阅了这个消息的客户端才能收到消息,实际上,信号也是可以点对点发送的(仅发给指定客户端),这个以后讨论,先讨论通常的广播信号;

  • 服务端发送信号的步骤

    1. 使用 dbus_bus_get() 连接到 D-Bus,获得一个连接 DBusConnection;
    2. 使用 dbus_message_new_signal() 为构建信号初始化一个 DBusMessge;
    3. 使用 dbus_message_append_args() 将信号参数添加到信号的 DBusMessage 中;
    4. 使用 dbus_connection_send() 将信号放入发送队列;
    5. 使用 dbus_connection_flush() 将发送队列的消息全部发送出去;
    6. 使用 dbus_message_unref() 释放信号的 DBusMessage;
  • 整个过程与在文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中描述的客户端向服务端请求一个服务的过程高度相似,但要简单一些:

    1. 请求服务时是客户端向服务端发出请求,而发送信号时是服务端发送一个广播消息;
    2. 请求服务时,服务端通常要在连接上绑定一个公用的名称(总线名称),客户端在请求服务时必须要指定这个总线名称,否则 D-Bus 不知道你要向哪个应用程序请求服务,发送信号时,服务端不一定需要在连接上绑定名称,因为通常客户端只需要接收信号,不需要向服务端发送任何消息;
    3. 请求服务时,客户端在发出请求后通常需要服务端的回复,发送信号时没有任何回复消息;
  • 客户端要接收到信号,需要订阅指定的信号,D-Bus 只会把你订阅的信号推送过来;

  • 使用 dbus_bus_add_match() 订阅信号:

    void dbus_bus_add_match(DBusConnection *connection,
                            const char *rule,
                            DBusError *error);
    
    • connection 是使用 dbus_bus_get() 获得的连接;
    • error 已经在很多函数调用中出现过,不多说了;
    • 这个 rule 参数是这个函数的灵魂,这是一个字符串,这个字符串定义了一个规则,告诉 D-Bus 我要订阅符合这个规则的信号;
    • 这个规则使用 key/value 的形式描述,可以有多个 key/value 对用于描述多个条件,每个 key/value 对用 “,” 分隔;
    • 举个 rule 的例子:"type='signal',sender='cn.whowin.dbus', path='/cn/whowin/dbus', interface='cn.whowin.dbus_iface',member='notify'"
    • 在这个例子中,type='signal' 表示消息类型为信号,sender 是发送方的总线名称,path 是发送方的对象路径,interface 是发送方的接口名称,member 是信号名称,D-Bus 会把符合这些条件的信号推送到订阅的进程中;
    • 在描述规则时不用把条件写的这么全,比如:"type='signal',sender='cn.whowin.dbus',path='/cn/whowin/dbus'",则从 cn.whowin.dbus 的对象 /cn/whowin/dbus 发出的消息都可以收到;
    • 在这个调用中,如果 error 参数为 NULL,则调用 dbus_bus_add_match() 后会立即返回,不会产生阻塞,但是订阅不会生效,需要执行 dbus_connection_flush(conn) 后订阅才会生效,而且如果发生了错误程序也是无法知晓的,所以,不建议这样做;
    • 目前 rule 可以使用的 key/value 对中的 key 可以为:type, sender, interface, member, path, destination;
    • destination 指的是目的地址,比如::275.6,在广播信号中通常用不上;
    • 下面这段代码订阅了一个信号:
    DBusError dbus_error;
    DBusConnection *conn;
    
    dbus_error_init(&dbus_error);
    conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_error);
    
    dbus_bus_add_match(conn, "type='signal',path='/cn/whowin/dbus/signal',interface='cn.whowin.dbus_iface'", &dbus_error);
    ......    
    
  • 如果有必要,你可以使用 dbus_bus_add_match() 订阅多个信号。

2 libdbus 的异步处理机制

  • 客户端并不知道什么时候会有信号发出来,所以为了能及时收到信号必须不断轮询,像这样:

    DBusConnection *conn;
    DBusMessage *message;
    ......
    while (dbus_connection_read_write_dispatch(conn, -1)) {
        // loop
        message = dbus_connection_pop_message(conn);
        if (message == NULL) {
            usleep(10000);
            continue;
        }
        if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) {
            usleep(10000);
            continue;
        }
        ......
    }    
    
  • 函数 dbus_connection_read_write_dispatch() 在文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中做过介绍;

  • 显然,这样的编程模式并不高效,尤其是当程序不仅仅是要接收信号,还有其他工作要做时,这种程序架构就显得更加不可接受;

  • 实际上,libdbus 还提供了另外一种异步接收信息的方式,像下面这样的代码:

    DBusHandlerResult signal_filter(DBusConnection *connection, DBusMessage *message, void *usr_data) {
        DBusError dbus_error;
        
        dbus_error_init(&dbus_error);
        if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) {
            printf("Client: This is not a signal.\n");
            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
        }
        ......
        return DBUS_HANDLER_RESULT_HANDLED;
    }
    
    int main() {
        DBusError dbus_error;
        DBusConnection *conn;
    
        dbus_error_init(&dbus_error);
        conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_error);
    
        dbus_connection_add_filter(conn, signal_filter, NULL, NULL);
        dbus_bus_add_match(conn, "type='signal',path='/cn/whowin/dbus/signal',interface='cn.whowin.dbus_iface'", &dbus_error);
        
        while (dbus_connection_read_write_dispatch(conn, -1)) {
            /* loop */
            ......
        }
        
        ......
        return;
    }
    
    • 在主程序中,首先使用 dbus_connection_add_filter() 添加了一个过滤器(Filter),然后再用 dbus_bus_add_match() 订阅感兴趣的信号;
    • 在下面的 while 循环中,并不需要去接收消息,当订阅的信号到来时,会直接调用过滤器,在过滤器里处理收到的信号即可;
    • 所以,上面这段程序实际上是在函数 signal_filter() 中处理信号;
  • 函数 dbus_connection_add_filter() 原型

    dbus_bool_t dbus_connection_add_filter(DBusConnection *connection,
                                           DBusHandleMessageFunction function,
                                           void *user_data,
                                           DBusFreeFunction free_data_function 
    )
    
    • connection 为通过 dbus_bus_get() 获得的连接;
    • function 为过滤器调用的函数;
    • user_data 为传递给 function 的参数;
    • free_data_function 为释放 user_data 需要调用的函数;
  • DBusHandleMessageFunction 的定义

    typedef DBusHandlerResult(* DBusHandleMessageFunction)(DBusConnection *connection,
                                                           DBusMessage *message,
                                                           void *user_data);    
    
    • 所以在 dbus_connection_add_filter() 中,function 是一个函数指针,该函数将接收三个参数,第一个是从 dbus_bus_get() 获得的连接,第二个参数是一个消息结构 DBusMessage,表示收到的消息,第三个参数是用户数据,在使用 dbus_connection_add_filter() 添加过滤器时设置;
    • 当一个过滤器函数被调用时,收到的消息已经在 message 中了;
    • 这个过滤器函数的返回值是 DBusHandlerResult,这是一个枚举类型,其值有三个:DBUS_HANDLER_RESULT_HANDLEDDBUS_HANDLER_RESULT_NOT_YET_HANDLEDDBUS_HANDLER_RESULT_NEED_MEMORY
    • DBUS_HANDLER_RESULT_HANDLED 表示该过滤器函数已经获得了一个有效消息并进行了处理,该消息无需再交给其他过滤器处理;
    • DBUS_HANDLER_RESULT_NOT_YET_HANDLED 表示该过滤器没有处理该消息,如果有其他过滤器,可以把该消息交给其他过滤器处理;
    • DBUS_HANDLER_RESULT_NEED_MEMORY 通常用不上;
  • 调用过滤器函数是由 libdbus 实现的,应该是在调用 dbus_connection_read_write_dispatch() 时,当有可读消息时,调用过滤器函数;

  • 调用过滤器函数后的返回值并不会返回到应用程序中,但是对其它过滤器可能会产生影响,当系统内有多个过滤器时,当前过滤器返回

    1. DBUS_HANDLER_RESULT_HANDLED 意味着已经处理好了这个消息,不必再使用其它过滤器处理该消息;
    2. DBUS_HANDLER_RESULT_NOT_YET_HANDLED 意味着这个消息没有在当前过滤器中被处理,如果有其它过滤器,应该尝试使用其它过滤器处理;
  • 所以,过滤器函数的返回一定要正确,否则会有消息丢失;

  • 如果有必要,你可以添加多个过滤器,去处理不同的消息;

  • 过滤器的概念,其实也不仅仅可以用在接收信号上,也可以用在调用方法上;

  • 尽管我们向 D-Bus 订阅了我们感兴趣的信号,但其实有时也会一些不符合订阅条件的信号到来,所以,在程序中还是要做一些判断,以确保收到的是我们期望的信号,如果不是,返回 DBUS_HANDLER_RESULT_NOT_YET_HANDLED,让其它过滤器去处理。

3 使用 libdbus 异步接收信号的实例

  • 源程序:dbus-signals.c(点击文件名下载源程序,建议使用UTF-8字符集)演示了使用 libdbus 对信号进行发送和接收,以及如何异步接收信号;

  • 该程序是一个多进程程序,建立了一个服务端进程和三个客户端进程;

  • 服务端进程在启动后发送出一个内容为 “start” 的信号,暂停 5 秒后,再发出一个内容为 “quit” 的信号,然后退出进程;

  • 服务端在发送信号时,其对象路径、接口名称和信号名称均相同;

  • 客户端进程订阅了服务端的信号,并添加了两个过滤器,一个用于处理内容为 “start” 的信号,另一个用于处理内容为 “quit” 的信号,这里仅是为了演示多个过滤器的工作方式;

  • 客户端检查收到的信号,如果其内容为 “quit”,则退出进程;

  • 编译:gcc -Wall -g dbus-signals.c -o dbus-signals `pkg-config --libs --cflags dbus-1`

  • 有关 pkg-config --libs --cflags dbus-1 可以参阅文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中的简要说明;

  • 运行:./dbus-signals

  • 运行截图:

    Screenshot of running dbus-signals

  • 程序运行后,客户端进程的两个过滤器都显示了 “Wrong object path” 的信息,这条信息是 D-Bus 为客户端连接分配了名称后发送过来的通知信号,虽然我们没有订阅,但 D-Bus 会强行推送过来;

  • 这条通知信号在经过过滤器时,过滤器返回了 “DBUS_HANDLER_RESULT_NOT_YET_HANDLED”,因为这个返回值导致这个消息在经过第一个过滤器后还会再进入第二个过滤器进行处理,如果过滤器在遇到对象路径不对时返回 “DBUS_HANDLER_RESULT_HANDLED”,则这条消息不会再去第二个过滤器,读者可以尝试修改程序看看是不是这样;

  • 如果你多次运行这个程序你会发现,信号总是首先到达 signal_quit() 过滤器,然后才到达 signal_start() 过滤器,这是因为我们先添加的 signal_quit() 过滤器,如果你改动一下程序,先添加 signal_start() 过滤器,再添加 signal_quit() 过滤器,你会看到信号到达的顺序也会发生变化。

欢迎订阅 『进程间通信专栏』


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/287151.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【VTK-Rendering::Core】第二期 vtkTextActor

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文以vtkTextActor为起点,分享VTK中Text相关的内容,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞关注,小易会继续努力分享,一起进步&a…

提升三维模型数据的几何坐标纠正速度效率具体技术方法

提升三维模型数据的几何坐标纠正速度效率具体技术方法 根据搜索结果,以下是提升倾斜摄影三维模型数据的几何坐标纠正和三维重建速度的具体技术方法: 1、增加控制点:通过增加控制点数量可以提高几何坐标精度。控制点是已知地面坐标的点&#…

儿童可以戴骨传导耳机吗?骨传导耳机对儿童有危害吗?

儿童是可以佩戴骨传导耳机的,相比于传统的入耳式蓝牙耳机,佩戴骨传导耳机要更健康一些。 首先骨传导耳机通过人体骨骼来传递声音,不经过耳道和耳膜,所以对听力的损伤较小,而且由于儿童还处于发育期,耳道和耳…

【并发设计模式】聊聊等待唤醒机制的规范实现

在多线程编程中,其实就是分工、协作、互斥。在很多场景中,比如A执行的过程中需要同步等待另外一个线程处理的结果,这种方式下,就是一种等待唤醒的机制。本篇我们来讲述等待唤醒机制的三种实现,以及对应的应用场景。 G…

{“sn“:““,“error“:3,“desc“:“VAD is not available“,“sub_error“:3100}解决办法

目录 问题描述: 解决顺序: 问题描述: 这个问题是在使用百度语音识别时出现的问题,当一切都配置好之后,启动程序,点击录音,发现程序并没有执行onEvent方法,直接闪退了,当断点调试时发现程序并没有进入onEvent方法,抛出异常{"sn":"","erro…

从0搭建github.io网页

点击跳转到🔗我的博客文章目录 从0搭建github.io网页 文章目录 从0搭建github.io网页1.成果展示1.1 网址和源码1.2 页面展示 2.new对象2.1 创建仓库 3.github.io仓库的初始化3.1 千里之行,始于足下3.2 _config.yml3.3 一点杂活 4.PerCheung.github.io.p…

2024/1/2 C++ work

全局变量,int monster 10000;定义英雄类hero,受保护的属性string name,int hp,int attck;公有的无参构造,有参构造,虚成员函数 void Atk(){blood-0;},法师类继承自英雄类,私有属性 …

k8s中实现pod自动扩缩容

一、k8s应用自动扩缩容概述 1)背景: 在实际的业务场景中,我们经常会遇到某个服务需要扩容的场景(例如:测试对服务压测、电商平台秒杀、大促活动、或由于资源紧张、工作负载降低等都需要对服务实例数进行扩缩容操作&…

gzip的了解

基本操作原理:通过消除文件中的冗余信息,使用哈夫曼编码等算法,将文件体积压缩到最小。这种数据压缩方式在网络传输中发扮了巨大作用,减小了传输数据的大小,从而提高了网页加载速度。 vue Vue CLI修改vue.config.js&a…

MySQL 临时表

MySQL 临时表 MySQL 临时表在我们需要保存一些临时数据时是非常有用的。 临时表只在当前连接可见,当关闭连接时,MySQL 会自动删除表并释放所有空间。 在 MySQL 中,临时表是一种在当前会话中存在的表,它在会话结束时会自动被销毁…

vue3按钮点击频率控制

现有一个按钮&#xff0c;如下图 点击时 再次点击 刷新窗口再次点击 刷新窗口依然可以实现点击频率控制。 代码实现&#xff1a; <template><!--<el-config-provider :locale"locale"><router-view/></el-config-provider>--><el…

Java学习苦旅(十六)——List

本篇博客将详细讲解Java中的List。 文章目录 预备知识——初识泛型泛型的引入泛型小结 预备知识——包装类基本数据类型和包装类直接对应关系装包与拆包 ArrayList简介ArrayList使用ArrayList的构造ArrayList常见操作ArrayList遍历 结尾 预备知识——初识泛型 泛型的引入 我…

机器人制作开源方案 | 多地形适应野外探索智能车

1. 作品基本介绍 如今&#xff0c;智能机器人在军事、制造业、交通运输、航天航空、医疗、服务等领域已有广泛的应用&#xff0c;智能车是机器人研究领域的一项重要基础内容&#xff0c;在各种移动机构中&#xff0c;最为常见的是轮式移动方式&#xff0c;当今社会正处于科技高…

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-9PID控制器

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-9PID控制器&#xff09; P —— Proportional I —— Integral D —— Derivative 当前误差/过去误差/误差的变化趋势 K p ⋅ e K_{\mathrm{p}}\cdot e Kp​⋅e&#xff1a;比…

C++基本语言:1.5结构、pbulic、private权限修饰符、类简介

C基本语言包含10章节内容&#xff0c;存于C从入门到精通专栏 目录 一、结构回顾 ①结构变量作为参数 ②采用引用 ③用指向结构体的指针做函数参数 问&#xff1a;C/C的结构有何区别&#xff1f; 二、public和private权限修饰符 三、类简介&#xff1a;类也是一种用户自…

EBU7140 Security and Authentication(三)密钥管理;IP 层安全

B3 密钥管理 密钥分类&#xff1a; 按时长&#xff1a; short term&#xff1a;短期密钥&#xff0c;用于一次加密。long term&#xff1a;长期密钥&#xff0c;用于加密或者授权。 按服务类型&#xff1a; Authentication keys&#xff1a;公钥长期&#xff0c;私钥短期…

【InnoDB数据存储结构】第1章节:数据页存储结构

目录结构 之前整篇文章太长&#xff0c;阅读体验不好&#xff0c;将其拆分为几个子篇章。 本篇章讲解 InnoDB 数据页的存储结构。 数据的存储结构 索引是在存储引擎中实现的&#xff0c;MySQL 服务器上的 存储引擎负责对表数据的读取和写入。 但是不同存储引擎对 数据存放格…

Open3D 最小二乘拟合平面——拉格朗日乘子法

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接Open3D 最小二乘拟合平面——拉格朗日乘子法。爬虫自重。 一、算法原理 设拟合出的平面方程为: a x + b y +

宣传照(私密)勿转发

精美的海报通常都是由UI进行精心设计的&#xff0c;现在有100 件商品需要进行宣传推广&#xff0c;如果每个商品都出一张图显然是不合理的&#xff0c;且商品信息各异。因此需要通过代码的形式生成海报。对此&#xff0c;我也对我宣传一波&#xff0c;企图实现我一夜暴富的伟大…

Nice Water Shader

非常好的水着色器! 标准RP上的新程序泡沫!!(URP即将推出) URP支持!! 有3个版本: -台式机 -移动设备 -桌面拼接 有灯光支持!! 使用 CUSTOM SHADER INSPECTOR(自定义着色器检查器) 个性化事物的能力,如: 镶嵌图案 3种不同颜色,形成渐变深度 色彩位置优势 菲涅耳颜色和…