导航:[首页]->[network]->[DBUS学习笔记之 DBusConnection]

DBusConnection是Dbus对连接的抽象,站在socket的角度上,我们可以理解为一条TCP连接,通过它可以发送和接收消息。

DBusConnection维护着两条队列,发送队列和接收队列。

##生命周期 通常我们使用dbus_bus_get来创建一个连接,第一个参数我们指定类型,

  1. DBUS_BUS_SESSION :The login session bus.
  2. DBUS_BUS_SYSTEM :The systemwide bus.

和dbus_bus_get相比,dbus_bus_get_private总是创建一个全新的DBusConnection,而dbus_bus_get总是去尝试打开一个已有的DBusConnection,并使用引用技术管理。

你也可以使用dbus_connection_open或者dbus_connection_open_private打开已有的连接。

和其他DBus对象一样,DBusConnection也使用引用技术,所以dbus_connection_unref即可销毁连接。

DBusConnection也可以使用DBusMessage类似的私有数据,具体见dbus_connection_allocate_data_slot系列函数。

默认情况下,若DBusConnection收到一个disconnect信号,会调用_exit()终止函数,可以使用dbus_connection_set_exit_on_disconnect修改此行为。

##发送消息 通常我们可以使用dbus_connection_send发送消息,不过和send,sendto之类的socket函数不一样的是,它并不会立即发送数据,而只是简单地将Message扔到发送队列。待主循环下次运行时被发送出去。为了强制立即发送,可以紧跟着一个dbus_connection_flush

if( !dbus_connection_send (connection,msg,&serial)){
    fprintf(stderr,"Out of Memory!\n");
    return -1;
}
dbus_connection_flush (connection);

##接收消息 接收消息根据需要我们可以使用同步或者异步的方式。

通常对于简单的程序,自己维护这消息循环,那么同步方式是最简单可行的方法。例如

while(1){
	dbus_connection_read_write(connection,0); 
	msg = dbus_connection_pop_message (connection);
}

dbus_connection_read_write阻塞当前线程,直到有消息过来,读取消息并解析数据成Message并放入接收队列再返回,接下来就使用dbus_connection_pop_message从接收队列中读取一条消息。

和dbus_connection_pop_message不同,dbus_connection_borrow_message获得消息之后,并不会从接收队列中删除。

除了dbus_connection_read_write之外,我们也可以使用dbus_connection_read_write_dispatch来阻塞并接收消息。和dbus_connection_read_write不同的是,dbus_connection_read_write_dispatch会额外的执行一次Dispatch操作(使用dbus_connection_dispatch)。

每一次dbus_connection_read_write_dispatch或者dbus_connection_dispatch调用只会从接收队列中处理一条Message

何为Dispatch呢?我们先了解下Dispatch的流程

  1. First, any method replies are passed to DBusPendingCall or dbus_connection_send_with_reply_and_block() in order to complete the pending method call.
  2. Second, any filters registered with dbus_connection_add_filter() are run. If any filter returns DBUS_HANDLER_RESULT_HANDLED then processing stops after that filter.
  3. Third, if the message is a method call it is forwarded to any registered object path handlers added with dbus_connection_register_object_path() or dbus_connection_register_fallback().

我们重点关注二三点。第二点是说我们可以注册一些过滤器(函数)来接收消息,这类似于MFC/WTL中的PreTranslateMessage。若在钩子函数中返回DBUS_HANDLER_RESULT_HANDLED,那么消息不会继续往下传递。第三点说可以注册一些钩子来关注特定的对象(使用对象路径匹配)。

DBusHandlerResult filter_func(DBusConnection *c, DBusMessage *msg,void *data) 
{
	return DBUS_HANDLER_RESULT_HANDLED;
}
char *init(void)
{
	dbus_connection_add_filter(connection, filter_func, NULL, NULL);
}

DBusHandlerResult message_handler(DBusConnection *connection, 
		DBusMessage *message, 
		void *user_data)
{
}
char *dbus_init(void)
{
	DBusObjectPathVTable dnsmasq_vtable = {NULL, &message_handler, NULL, NULL, NULL, NULL };
	if (!dbus_connection_register_object_path(connection,  DNSMASQ_PATH, 
				&dnsmasq_vtable, NULL))
		return _("could not register a DBus message handler");
}

dbus_connection_register_object_path用于关注特定的对象,而dbus_connection_register_fallback可以认为是通配符,匹配路径的一个子集。

##异步接收 然而更多的时候,我们希望挂在其他主循环工作,例如一个GUI事件循环,或者一个socket多路IO等待(epoll/select)。此时我们就必须使用异步方式。

具体做法是,使用dbus_connection_set_watch_functions注册DBusWatch监视函数。当DBus创建句柄(例如socket句柄),那么注册的会调会被触发,此时我们可以将这些句柄加入到我们自己的事件循环中,例如下例中的libevent。

当使用自定义的消息循环监听到IO事件后,我们需要调用dbus_watch_handle来通知dbus,这个过程可以理解为“外部接口告诉DBus有数据可以读取,然后Dbus就读取这些数据并解析之,最后塞入接收队列”。

接收之后,我们还要继续处理这些消息。可以简单的使用dbus_connection_read_write。更多时候我们使用dispatch接口(钩子)。

若依赖于定时器,我们还需要实现DBusTimeout接口。具体使用dbus_connection_set_timeout_function。DBusTimeout并非libevent之类的定时器,而是Dbus自己需要使用定时器,但是又不控制主循环,所以只好在需要定时器的时候触发dbus_connection_set_timeout_function的回调,通知上层帮它实现定时器。当上层实现的定时器到期之后,再调用dbus_timeout_handle通知Dbus,这点和dbus_watch_handle类似。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "event2/event.h"
#include "event2/event_compat.h"
#include "event2/event_struct.h"

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

static struct watch * s_watchs_;
struct watch {
	DBusWatch *watch;
	struct event event;
	struct watch *next;
	void* data;
};

static DBusHandlerResult filter_func(DBusConnection *c, DBusMessage *msg,
		void *data) {
	printf("if: %s, member: %s, path: %s\n",
			dbus_message_get_interface(msg),
			dbus_message_get_member(msg),
			dbus_message_get_path(msg));


	DBusMessageIter arg;
	char * sigvalue;
	if (dbus_message_is_signal(msg, "test.signal.Type", "Test")) {
		if (!dbus_message_iter_init(msg, &arg))
			fprintf(stderr, "Message Has no Param");
		else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
			printf("Param is not string");
		else
			dbus_message_iter_get_basic(&arg, &sigvalue);
		printf("Got Singal with value : %s\n", sigvalue);
	} else if (dbus_message_is_signal(msg, "test.signal.Type1", "Test")) {
		if (!dbus_message_iter_init(msg, &arg))
			fprintf(stderr, "Message Has no Param");
		else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
			printf("Param is not string");
		else
			dbus_message_iter_get_basic(&arg, &sigvalue);
		printf("Got Singal with value1 : %s\n", sigvalue);
	} else {
		printf("fuck\n");
	}

	return DBUS_HANDLER_RESULT_HANDLED;
}

static void            udp_cb(int fd, short event, void *argc)
{
	printf("recv msg '%d'\n",__LINE__);
	struct watch * w = (struct watch*)argc;

	int flags = 0;
	if (event | EV_READ)
		flags |= DBUS_WATCH_READABLE;

	if (event | EV_WRITE)
		flags |= DBUS_WATCH_WRITABLE;

	if (flags != 0)
		dbus_watch_handle(w->watch, flags);

	DBusConnection *connection = (DBusConnection *)w->data;
	if (connection) {
		dbus_connection_ref(connection);
		DBusDispatchStatus ret = 0;
		do
		{
			ret = dbus_connection_dispatch(connection);
		}while ( ret == DBUS_DISPATCH_DATA_REMAINS);
		dbus_connection_unref(connection);
	}
}

static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
	int fd = dbus_watch_get_unix_fd(watch);
	int flags = dbus_watch_get_flags(watch);
	printf("add watch %d\n",fd);
	struct watch *w;

	if (!(w = malloc(sizeof(struct watch))))
		return FALSE;

	w->watch = watch;
	w->next = s_watchs_;
	s_watchs_ = w;

	event_set(
			&(w->event),
			dbus_watch_get_unix_fd(watch),
			EV_PERSIST | EV_READ,
			udp_cb,
			w);
	event_add(&(w->event), NULL);

	w->data = data; /* no warning */
	return TRUE;
}

static void remove_watch(DBusWatch *watch, void *data) {
	printf("remove watch %d\n",__LINE__);
	struct watch **up, *w, *tmp;

	for (up = &(s_watchs_), w = s_watchs_; w; w = tmp) {
		tmp = w->next;
		if (w->watch == watch) {
			*up = tmp;
			free(w);
		} else
			up = &(w->next);
	}

	w = data; /* no warning */
}

void listen_signal() {
	DBusMessage * msg;
	DBusMessageIter arg;
	DBusConnection * connection;
	DBusError err;
	int ret;
	char * sigvalue;

	//步骤1:建立与D-Bus后台的连接
	dbus_error_init(&err);
	connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
	dbus_connection_flush(connection);
	dbus_connection_add_filter(connection, filter_func, NULL, NULL);
	dbus_connection_set_watch_functions(connection, add_watch, remove_watch,
			NULL, connection, NULL);
}

int main(int argc, char ** argv) {
	struct event_base *base = event_init();
	listen_signal();
	event_base_dispatch(base);
	return 0;
}

##调试工具

dbus-send可以用来发送消息和方法调用

dbus-send  --session --type=signal /test/signal/Object test.signal.Type.Test string:"hello world"

//步骤3:发送一个信号
if((msg = dbus_message_new_signal ("/test/signal/Object","test.signal.Type","Test")) == NULL){                                   
    fprintf(stderr,"Message NULL\n");
    return -1; 
}   
//给这个信号(messge)具体的内容 
const char* sigvalue = "hello world"
dbus_message_iter_init_append (msg,&arg);
if(!dbus_message_iter_append_basic (&arg,DBUS_TYPE_STRING,&sigvalue)){
    fprintf(stderr,"Out Of Memory!\n");
    return -1; 
} 

dbus-monitor可以监视总线上流动的消息。

##参考

  1. http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
  2. http://stackoverflow.com/questions/9378593/dbuswatch-and-dbustimeout-examples