长春网长春网站建设络推广,wordpress栏目页打不开,wordpress删除导入xml,金泉网做网站IPC 是 Linux 编程中一个重要的概念#xff0c;IPC 有多种方式#xff0c;本 IPC 系列文章的前十篇介绍了几乎所有的常用的 IPC 方法#xff0c;每种方法都给出了具体实例#xff0c;前面的文章里介绍了 D-Bus 的基本概念以及调用远程方法的实例#xff0c;本文介绍 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)有许多类似的地方这里简要回顾一下在上一篇文章中讨论的调用方法的概念 服务程序连接 D-Bus(dbus-daemon)获得一个连接D-Bus 随机给这个连接分配一个唯一名称为该连接绑定一个固定的名称(Bus Name)以方便客户端访问这个连接总线名称通常以反向域名的形式命名在该连接下可以建立一个或多个对象(Object)对象名称以路径(类似文件系统路径)表示在每个对象上可以建立一个或者多个接口(Interface)接口的名称也是使用反向域名的命名方式每个接口下可以有一个或者多个方法(Method)客户端需要请求服务端的某个方法时需要知道总线名称、对象路径、接口名称以及方法名称并将调用参数传送给服务端 信号(Signal)也是建立在一个接口下一个接口下不仅可以有一个或者多个方法还可以有一个或者多个信号 1 – 4 同上 每个接口下可以有一个或者多个信号(Signal)信号的命名与调用方法一样没有很多规则比如sig_demo客户端想要收到某个信号时需要向总线注册告知总线感兴趣的信号(包括对象路径、接口名称和信号名称)只能收到向总线注册过的信号 所以其实一个接口下可以有若干个方法和信号除此之外接口下还可以有若干个属性(Properties)方法、信号、属性组合在一起构成一个接口 本文仅讨论接口有关属性的事情以后的文章中再讨论 发送信号通常是服务端的事情信号通常是以广播的方式发出而只有订阅了这个消息的客户端才能收到消息实际上信号也是可以点对点发送的(仅发给指定客户端)这个以后讨论先讨论通常的广播信号 服务端发送信号的步骤 使用 dbus_bus_get() 连接到 D-Bus获得一个连接 DBusConnection使用 dbus_message_new_signal() 为构建信号初始化一个 DBusMessge使用 dbus_message_append_args() 将信号参数添加到信号的 DBusMessage 中使用 dbus_connection_send() 将信号放入发送队列使用 dbus_connection_flush() 将发送队列的消息全部发送出去使用 dbus_message_unref() 释放信号的 DBusMessage 整个过程与在文章 《IPC之十一使用D-Bus实现客户端向服务端请求服务的实例》 中描述的客户端向服务端请求一个服务的过程高度相似但要简单一些 请求服务时是客户端向服务端发出请求而发送信号时是服务端发送一个广播消息请求服务时服务端通常要在连接上绑定一个公用的名称(总线名称)客户端在请求服务时必须要指定这个总线名称否则 D-Bus 不知道你要向哪个应用程序请求服务发送信号时服务端不一定需要在连接上绑定名称因为通常客户端只需要接收信号不需要向服务端发送任何消息请求服务时客户端在发出请求后通常需要服务端的回复发送信号时没有任何回复消息 客户端要接收到信号需要订阅指定的信号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 的例子typesignal,sendercn.whowin.dbus, path/cn/whowin/dbus, interfacecn.whowin.dbus_iface,membernotify在这个例子中typesignal 表示消息类型为信号sender 是发送方的总线名称path 是发送方的对象路径interface 是发送方的接口名称member 是信号名称D-Bus 会把符合这些条件的信号推送到订阅的进程中在描述规则时不用把条件写的这么全比如typesignal,sendercn.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, destinationdestination 指的是目的地址比如: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, typesignal,path/cn/whowin/dbus/signal,interfacecn.whowin.dbus_iface, dbus_error);
...... 如果有必要你可以使用 dbus_bus_add_match() 订阅多个信号。
2 libdbus 的异步处理机制 客户端并不知道什么时候会有信号发出来所以为了能及时收到信号必须不断轮询像这样 DBusConnection *conn;
DBusMessage *message;
......
while (dbus_connection_read_write_dispatch(conn, -1)) {// loopmessage 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, typesignal,path/cn/whowin/dbus/signal,interfacecn.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_HANDLED、DBUS_HANDLER_RESULT_NOT_YET_HANDLED 和 DBUS_HANDLER_RESULT_NEED_MEMORYDBUS_HANDLER_RESULT_HANDLED 表示该过滤器函数已经获得了一个有效消息并进行了处理该消息无需再交给其他过滤器处理DBUS_HANDLER_RESULT_NOT_YET_HANDLED 表示该过滤器没有处理该消息如果有其他过滤器可以把该消息交给其他过滤器处理DBUS_HANDLER_RESULT_NEED_MEMORY 通常用不上 调用过滤器函数是由 libdbus 实现的应该是在调用 dbus_connection_read_write_dispatch() 时当有可读消息时调用过滤器函数 调用过滤器函数后的返回值并不会返回到应用程序中但是对其它过滤器可能会产生影响当系统内有多个过滤器时当前过滤器返回 DBUS_HANDLER_RESULT_HANDLED 意味着已经处理好了这个消息不必再使用其它过滤器处理该消息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 运行截图 程序运行后客户端进程的两个过滤器都显示了 “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() 过滤器你会看到信号到达的顺序也会发生变化。
欢迎订阅 『进程间通信专栏』