在『进程间通信』系列文章中前面已经有三篇关于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 编程的初学者阅读。
dbus-daemon
来管理总线,服务进程通过一种传统 IPC 方法与 dbus-daemon
进行通信,客户进程可能通过另一种 IPC 方式与 dbus-daemon
进行通信,dbus-daemon
对客户进程与服务进程之间的消息进行转发;添加 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()
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)
dbus_bus_get()
获取的连接add_function()
、remove_function()
和 toggled_function()
的用户参数;函数 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()
dbus_bool_t dbus_watch_get_enabled(DBusWatch *watch);
函数 dbus_watch_get_unix_fd()
int dbus_watch_get_unix_fd(DBusWatch *watch);
函数 dbus_watch_get_flags()
unsigned int dbus_watch_get_flags(DBusWatch *watch);
DBUS_WATCH_READABLE
和 DBUS_WATCH_WRITABLE
;DBUS_WATCH_READABLE
表示对文件描述符的’读’进行监视;DBUS_WATCH_WRITABLE
表示对文件描述符的’写’进行监视;函数 dbus_watch_handle()
dbus_bool_t dbus_watch_handle(DBusWatch *watch,
unsigned int flags);
dbus_watch_handle()
将把数据从从文件描述符中读出,解析为 D-Bus 的消息,并把消息放入消息队列,然后才可以用 libdbus 库的 API 从队列中读出消息并进行处理,如果不调用 dbus_watch_handle()
,则文件描述符上将一直处于有数据可读状态;dbus_watch_handle()
返回为 FALSE 时表示执行失败,执行失败的原因只有一个,就是内存不够,如果忽略返回的 FALSE,通常不会有致命问题,直至有足够内存时,dbus_watch_handle()
便会返回 TRUE;dbus_connection_set_watch_functions()
设置的 watch,也就是系统在调用 add_function()
时传递过来的 watch;dbus_watch_get_flags()
返回的值含义相同,DBUS_WATCH_READABLE
表示文件描述符中有可读数据,DBUS_WATCH_WRITABLE
表示文件描述符中有可写数据;函数 dbus_connection_get_dispatch_status()
DBusDispatchStatus dbus_connection_get_dispatch_status(DBusConnection *connection);
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()
"借用"的消息"还"回到消息队列中;函数 dbus_connection_steal_borrowed_message()
void dbus_connection_steal_borrowed_message(DBusConnection *connection,
DBusMessage *message);
dbus_connection_borrow_message()
"借用"的消息从消息队列中读取出来;其它在实例中用到的函数已经在前面几篇关于 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()
借用消息,然后根据其消息类型、对象路径、接口名称等将消息分发给其它函数,需要丢弃的消息则将其读出直接丢弃;源程序: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
运行截图: