IPC之十四:使用libdbus通过select()接收D-Bus消息的实例
在『进程间通信』系列文章中前面已经有三篇关于D-Bus的文章,本文继续讨论D-Bus;libdbus抽象了实现IPC时实际使用的方式(管道、socket等),libdbus允许在一个D-Bus连接上添加一个watch,通过watch对实际IPC中使用的文件描述符进行监视,本文讨论了如何在D-Bus连接中添加watch,如何使用在socket编程中常用的select从D-Bus返回的文件描述符中接收到D-Bus消息,本文给出了具体实例,附有完整的源代码;本文实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文不适合 Linux 编程的初学者阅读。
1 基本概念
- 阅读本文前,建议先阅读前面的关于 D-Bus 的三篇文章:《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》、《IPC之十二:使用libdbus在D-Bus上异步发送/接收信号的实例》 和 《IPC之十三:使用libdbus通过D-Bus请求系统调用实现域名解析的实例》
- 在文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中介绍了 D-Bus 的一些基本概念,介绍了 libdbus 库的一些基本 API,在实例中使用 libdbus 库的 APIs 实现了客户进程向服务进程请求服务的过程;
- 在文章 《IPC之十二:使用libdbus在D-Bus上异步发送/接收信号的实例》 中,我们介绍了 D-Bus 中信号的概念,在实例中使用 libdbus 库的 match 订阅了信号,并使用过滤器(filter)异步接收信号;
- 在文章 《IPC之十三:使用libdbus通过D-Bus请求系统调用实现域名解析的实例》 中,我们介绍了如何通过 D-Bus 调用系统服务,有很多系统服务都有 D-Bus 接口,透过这个接口,应用程序可以调用系统服务,从而快速地实现一些复杂的功能;
- 在『进程间通信』系列文章中的前十篇文章中,介绍了许多 IPC 的方法,有管道、共享内存、UNIX DOMAIN SOCKET 等,其实 D-Bus 在实现 IPC 时也是用其中的一种方法实现的,但是 libdbus 将具体的实现抽象化了,所以,我们并不知道具体用的那种方法;
- D-Bus 通过
dbus-daemon
来管理总线,服务进程通过一种传统 IPC 方法与dbus-daemon
进行通信,客户进程可能通过另一种 IPC 方式与dbus-daemon
进行通信,dbus-daemon
对客户进程与服务进程之间的消息进行转发; - 实际上,我们也确实无需知道具体的实现方法,在 Linux 系统下,有所谓"一切皆文件"的原则,不管是管道还是 socket,最终返回给应用程序的都是一个文件描述符(File descriptor);
- 在进行 socket 编程时,服务端在处理多个连接请求时,常常会使用 select(),将正在侦听的多个 socket 的文件描述符放到一个’集’中,当’集’中的某个文件描述符有可读数据时,select() 就会返回,然后应用程序就可以对相应的文件描述符做出处理;
- 如果在 D-Bus 编程中也能通过 select() 来处理消息的话,将给编程带来很大方便,当应用程序中要处理 D-Bus 消息、socket 通信以及文件读写时,使用 select() 可以合并处理,这将大大提高程序的效率;
2 在D-Bus中与 watch 相关的函数介绍
-
添加 watch 的目的就是为了监视实际实现 IPC 的文件描述符,watch 添加成功才可以获取实际使用的文件描述符,从而实现在文件描述符一级的监视;
-
libdbus 库的 API 调用手册:D-Bus low-level public API
-
本节并不会介绍所有与 watch 相关的函数,仅介绍在本文实例中用到的函数;
-
使用
dbus_bus_get()
连接到 dbus-daemon,使用dbus_bus_request_name()
获取总线名称都在以前的文章中做过介绍; -
函数
dbus_connection_set_watch_functions()
- 设置 watch 相关的函数
dbus_bool_t dbus_connection_set_watch_functions(DBusConnection *connection, DBusAddWatchFunction add_function, DBusRemoveWatchFunction remove_function, DBusWatchToggledFunction toggled_function, void *data, DBusFreeFunction free_data_function)
- connection:通过
dbus_bus_get()
获取的连接 - add_function:添加 watch 时执行的函数
- remove_function:删除 watch 时执行的函数
- toggled_function:切换 watch 时执行的函数
- data:传递给
add_function()
、remove_function()
和toggled_function()
的用户参数; - free_data_function:释放 data 的函数;
-
函数
DBusAddWatchFunction add_function
-
DBusAddWatchFunction
的定义为:typedef dbus_bool_t(* DBusAddWatchFunction) (DBusWatch *watch, void *data)
-
所以
add_function()
函数的格式应该为:dbus_bool_t add_function(DBusWatch *watch, void *data) { ........ }
-
当调用
dbus_connection_set_watch_functions()
时,add_function()
将被自动调用,watch
将由系统传递给函数,data
是传递给add_function()
的用户数据,在调用dbus_connection_set_watch_functions()
时由参数data
指定; -
watch 的数据类型是 DBusWatch,实际上就是一个结构,C 语言不是一个面向对象的语言,当要实现对象时,通常都是使用数据结构,一般情况下,无需了解其数据结构的具体情况;
-
系统传入的
watch
要保存好,因为在应用程序的其它地方可能需要它; -
用户传入的
data
可以是任何变量或者结构,在实例中会有具体的做法; -
在
add_function()
中通常需要判断 watch 是否被启用,然后通过调用dbus_watch_get_unix_fd()
获取被监视的文件描述符; -
add_function()
返回 FALSE 时表示内存不够,所以通常情况下即便出现错误也不要返回 FALSE;
-
-
函数
DBusRemoveWatchFunction remove_function
-
DBusRemoveWatchFunction
的定义为:typedef void(* DBusRemoveWatchFunction) (DBusWatch *watch, void *data)
-
所以
remove_function()
函数的格式应该为:void remove_function(DBusWatch *watch, void *data) { ........ }
-
在一个 watch 被禁用时,通常需要调用
remove_function()
,一般情况下,当一个 watch 被禁用时系统会调用toggled_function()
,然后在toggled_function()
中调用remove_funcion()
; -
data 参数与
add_function()
中的 data 参数一样,在设置时指定;
-
-
函数
DBusWatchToggledFunction toggled_function
-
DBusWatchToggledFunction
的定义为:typedef void(* DBusWatchToggledFunction) (DBusWatch *watch, void *data)
-
所以
toggled_function()
函数的格式应该为:void toggled_function(DBusWatch *watch, void *data) { ........ }
-
当 watch 被启用或者禁用时,系统通过调用
toggled_function()
来通知应用程序,系统将发生变化的 watch 作为参数传给toggled_function()
; -
data 参数与
add_function()
中的 data 参数一样,在设置时指定; -
在
toggled_function()
中,通常需要通过dbus_watch_get_enabled()
判断 watch 是启用还是禁用,如果是启用,调用add_function()
,如果是禁用则调用remove_function()
;
-
-
函数
dbus_watch_get_enabled()
- 检查 watch 是否已启用
dbus_bool_t dbus_watch_get_enabled(DBusWatch *watch);
- 返回 TRUE 表示 watch 已经启用,FALSE 表示 watch 已经禁用;
-
函数
dbus_watch_get_unix_fd()
int dbus_watch_get_unix_fd(DBusWatch *watch);
- 返回 watch 监视的文件描述符,这个描述符可能是管道、socket,也可能是其它类型的描述符;
-
函数
dbus_watch_get_flags()
unsigned int dbus_watch_get_flags(DBusWatch *watch);
- 获取 watch 对文件描述符的监视内容;
- 返回值可能有两个标志:
DBUS_WATCH_READABLE
和DBUS_WATCH_WRITABLE
; DBUS_WATCH_READABLE
表示对文件描述符的’读’进行监视;DBUS_WATCH_WRITABLE
表示对文件描述符的’写’进行监视;- 默认 watch 会监视文件描述符的错误状态(DBUS_WATCH_ERROR)和挂起状态(DBUS_WATCH_HANGUP),所以这两个标志不会在这个函数的返回值中出现;
-
函数
dbus_watch_handle()
dbus_bool_t dbus_watch_handle(DBusWatch *watch, unsigned int flags);
- 当 watch 所监视的文件描述符有可读取的数据时,需要调用此函数通知 libdbus 库;
- 当被监视的文件描述符中有数据可以读取时,调用函数
dbus_watch_handle()
将把数据从从文件描述符中读出,解析为 D-Bus 的消息,并把消息放入消息队列,然后才可以用 libdbus 库的 API 从队列中读出消息并进行处理,如果不调用dbus_watch_handle()
,则文件描述符上将一直处于有数据可读状态; dbus_watch_handle()
返回为 FALSE 时表示执行失败,执行失败的原因只有一个,就是内存不够,如果忽略返回的 FALSE,通常不会有致命问题,直至有足够内存时,dbus_watch_handle()
便会返回 TRUE;- 参数 watch 为使用
dbus_connection_set_watch_functions()
设置的 watch,也就是系统在调用add_function()
时传递过来的 watch; - 参数 flags 与函数
dbus_watch_get_flags()
返回的值含义相同,DBUS_WATCH_READABLE
表示文件描述符中有可读数据,DBUS_WATCH_WRITABLE
表示文件描述符中有可写数据;
-
函数
dbus_connection_get_dispatch_status()
DBusDispatchStatus dbus_connection_get_dispatch_status(DBusConnection *connection);
- 获取接收消息队列的当前状态;
- 其返回值 DBusDispatchStatus 是一个枚举类型,其值有三个:
DBUS_DISPATCH_DATA_REMAINS
:表示接收消息队列中可能有消息;DBUS_DISPATCH_COMPLETE
:表示接收消息队列为空;DBUS_DISPATCH_NEED_MEMORY
:表示可能有消息,但如果没有更多内存,则无法确定是否有消息;
- 当返回状态为
DBUS_DISPATCH_DATA_REMAINS
时,表示接收队列中有消息,或者正在将原始数据转换为 D-Bus 的消息,此时会返回DBUS_DISPATCH_DATA_REMAINS
状态,但由于消息还没有解析完毕,所以队列中还没有完整的消息;
-
函数
dbus_connection_borrow_message()
DBusMessage *dbus_connection_borrow_message(DBusConnection *connection);
- 这个函数很有意思,它从消息队列中"借用"一个消息,"借用"消息和读取消息的区别在于"借用"的消息不会从消息队列中删除,而读取的消息会从消息队列中删除;
- 当一个消息被"借用"以后,这条消息尽管还在队列中,但会被锁定,其它进程(线程)将无法再读取该消息,所以"借用"的消息要么尽快"还"回来,要么尽快读取出来,否则会影响消息的处理;
- 之所以要"借用"消息,是因为不能确定这条消息是否应该由当前程序处理,所以要"借用"出来做一下判断,如果不该当前程序处理,则立即"还"回去,否则应该立即将该消息读出来;
-
函数
dbus_connection_return_message()
void dbus_connection_return_message(DBusConnection *connection, DBusMessage *message);
- 该函数将由函数
dbus_connection_borrow_message()
"借用"的消息"还"回到消息队列中; - 参数 message 是要"还"回去的消息;
- 该函数将由函数
-
函数
dbus_connection_steal_borrowed_message()
void dbus_connection_steal_borrowed_message(DBusConnection *connection, DBusMessage *message);
- 该函数将由函数
dbus_connection_borrow_message()
"借用"的消息从消息队列中读取出来; - 参数 message 为要读出的消息,该函数执行完成后,该消息将从消息队列中被删除;
- 该函数将由函数
-
其它在实例中用到的函数已经在前面几篇关于 D-Bus 的文章中做过介绍,这里不再赘述;
3 使用 select() 处理 D-Bus 消息的步骤
-
使用
dbus_bus_get()
连接到 dbus-daemon,使用会话总线; -
使用
dbus_bus_request_name()
设置总线名称,以便客户端可以访问到; -
使用
dbus_connection_set_watch_functions()
添加一个 watch,考虑将一个结构传给设置的 watch 函数,用于存储 watch 的相关信息;struct watch_struct { DBusWatch *watched_watch; // Currently used watch int watched_rd_fd; // Readable file descriptor being watched int watched_wr_fd; // Writable file descriptor being watched }watch_fds; dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, (void *)&watch_fds, NULL))
-
为添加 watch 编写三个函数:
add_watch()
、remove_watch()
和toggle_watch()
;add_wath()
中,首先使用dbus_watch_get_enabled()
判断添加的 watch 是否已经启用,如未启用则无法继续,然后使用dbus_watch_get_unix_fd()
获取被监视的文件描述符,使用dbus_watch_get_flags()
判断被监视的这个描述符是可读还是可写,并保存好相关信息;static dbus_bool_t add_watch(DBusWatch *w, void *data) { if (!dbus_watch_get_enabled(w)) { // Returns whether a watch is enabled or not. return TRUE; } struct watch_struct *watch_fds = (struct watch_struct *)data; int fd = dbus_watch_get_unix_fd(w); // Returns a UNIX file descriptor to be watched, // which may be a pipe, socket, or other type of descriptor. unsigned int flags = dbus_watch_get_flags(w); // Gets flags from DBusWatchFlags indicating what conditions // should be monitored on the file descriptor. if (flags & DBUS_WATCH_READABLE) { // the watch is readable watch_fds->watched_rd_fd = fd; } if (flags & DBUS_WATCH_WRITABLE) { // the watch is writable watch_fds->watched_wr_fd = fd; } watch_fds->watched_watch = w; return TRUE; }
remove_watch()
的主要作用就是在禁用 watch 时可以释放在执行add_watch()
时所占用的资源;static void remove_watch(DBusWatch *w, void *data) { struct watch_struct *watch_fds = (struct watch_struct *)data; watch_fds->watched_watch = NULL; watch_fds->watched_rd_fd = 0; watch_fds->watched_wr_fd = 0; }
toggle_watch()
首先使用dbus_watch_get_enabled()
判断系统传过来的 watch 时禁用还是启用状态,如果是禁用状态,调用remove_watch()
,如果是启用状态,调用add_watch()
;static void toggle_watch(DBusWatch *w, void *data) { if (dbus_watch_get_enabled(w)) { add_watch(w, data); } else { remove_watch(w, data); } }
-
初始化
select()
的文件描述符集,设置select()
超时时间,执行select()
;fd_set readfds; // Reading fd set, Writing fd set and ... fd set int max_fd = 0; int activity; FD_ZERO(&readfds); if ((watch_fds.watched_watch != NULL) && (watch_fds.watched_rd_fd > 0)) { FD_SET(watch_fds.watched_rd_fd, &readfds); // set watched FD into fd set max_fd = watch_fds.watched_rd_fd; } else { exit(EXIT_FAILURE); } struct timeval timeoutval = {5, 0}; // timeout is 5 seconds activity = select(max_fd + 1, &readfds, NULL, NULL, &timeoutval);
-
文件描述符上有可读数据时,调用
dbus_watch_handle()
和消息接收函数select_recv()
if (FD_ISSET(watch_fds.watched_rd_fd, &readfds)) { if (dbus_watch_handle(watch_fds.watched_watch, DBUS_WATCH_READABLE)) { select_recv(conn); } }
- 调用
dbus_watch_handle()
的目的是把文件描述符上的数据读出并解析为 D-Bus 消息放入消息队列; - 如果
dbus_watch_handle()
返回 FALSE 表示需要更多的内存才能解析数据,此时文件描述符仍然处于有可读数据的状态,所以下次执行select()
仍可以处理数据,不会有灾难后果,也可以在dbus_watch_handle()
返回 FALSE 后做一个短延时(比如100ms);
- 调用
-
接收消息程序
select_recv()
- 这个程序是需要自己编写的,其目的就是接收、处理消息并向客户端发送回复消息;
- 首先使用
dbus_connection_get_dispatch_status()
检查状态,如果不是DBUS_DISPATCH_DATA_REMAINS
表示消息队列中没有消息,则不能继续进行; - 这个接收函数的通常做法是先使用
dbus_connection_borrow_message()
借用消息,然后根据其消息类型、对象路径、接口名称等将消息分发给其它函数,需要丢弃的消息则将其读出直接丢弃; - 本文实例中仅留下了需要处理的消息,实际上丢弃了所有其它的消息;
- 在这个函数中要处理到所有消息类型的消息,自行处理、分发到其他函数处理或者丢弃,不处理的话无法处理消息队列中的下一条消息;
3 实例
-
源程序:dbus-select.c (点击文件名下载源程序,建议使用UTF-8字符集)演示了使用 libdbus 通过
select()
接收方法调用消息的过程; -
该程序与文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中的实例 dbus-methods.c 完成相同的任务,其中客户端进程的程序完全一样;
-
与 dbus-methods.c 不同的是服务端进程,dbu-select.c 在服务端进程为 D-Bus 连接添加了一个 watch,使用
dbus_watch_get_unix_fd()
从 watch 中获取实际使用的文件描述符; -
当文件描述符上有数据可以读取时,调用接收程序读取消息并处理消息,然后向客户端发出回复消息;
-
该程序是个多进程程序,建立了一个服务端进程和若干个(默认为 3 个)客户端进程,服务端进程执行的函数为:
dbus-server()
,客户端进程执行的函数为:dbus-client()
; -
客户端进程向服务端进程的三个方法分别发出请求,服务端进程一一做出回复,客户端进程在调用完
quit
方法并收到服务端回复后主动结束进程; -
服务端进程在收到所有客户端进程发来的对
quit
方法的请求消息并做出回复后主动退出进程; -
编译:
gcc -Wall -g dbus-select.c -o dbus-select `pkg-config --libs --cflags dbus-1`
-
有关
pkg-config --libs --cflags dbus-1
可以参阅文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中的简要说明; -
运行:
./dbus-select
-
运行截图:
欢迎订阅 『进程间通信专栏』
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!