DBus研究笔记(一)

旧街凉风 提交于 2019-12-06 07:42:57
一.建立连接
        要使用DBus进行通信必须首先与系统建立连接, 并申请一个"域名"使得其他应用可以找到你。常用DBusConnection* dbus_bus_get(DBusBusType, DBusError*)系列函数来与bus daemon建立连接。DBusBusType为需要连接的总线类型有系统总线,会话总线,和DBUS_BUS_STARTER。系统总线顾名思义在整个系统级别都有效,而会话总线只是相对于当前登录的用户。
        需要说明的问题有两个:第一.dbus_bus_get可以被调用多次当用户,不想使用这个连接的时候需要需要调用dbus_connection_unref解除引用,原因在于,dbus_bus_get这个接口获取的连接不是私有的,不能直接调用dbus_connection_close关闭连接。第二.如果dbus_bus_get获得的是一个新的连接那么dbus_bus_get内部会调用dbus_connection_set_exit_on_disconnection()接口当用户把connection解除引用的时候,应用程序会自动退出。一般我们会在dbus_bus_get之后调用接口撤销该设置。

        其实dbus_bus_get所做的事情远比我们以为的复杂的多,首先做的第一件事情是,初始化bus_connection_addresses这个指针数组,指针指向的是总线地址,主要是通过环境变量来获取三种总线连接的地址,不同系统会有不一样的配置。根据用户指定需要连接的总线类型指定分别建立一张Hash表来管理DBusConnection。第二步:调用dbus_bus_register接口主要做的事情是调用一个系统标准的method叫"Hello"来获取一个unique name。

//建立连接
dbus_error_init(&err);
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if(!conn && dbus_error_is_set(&err))
{
	printf("Error: %s\n", err.message);
	return (-1);
}
//设置为当收到disconnect信号的时候不退出应用程序(_exit())
dbus_connection_set_exit_on_disconnect(conn, FALSE);


二.注册公共名
      主要用的是dbus_bus_request_name接口,需要注意的是第三个标志位一般设置为DBUS_NAME_FLAG_REPLACE_EXISTING表示当前用户独占该名称,可以用返回值和DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER比较判断是否获取成功。

dbus_bus_request_name里面也用到了标准method:"RequestName"来申请名称。

//请求获取公共名
nRet = dbus_bus_request_name(conn, CLIENT_WELL_NAME, DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != nRet)
{
	printf("request name failed \n");
	return (-1);
}


三.信号收发
       我们知道DBus里面的Signal类似于广播的性质,发送者把消息以及附加信息发送给deamon系统进程,系统进程根据该信号的等待队列依次分发消息。这就要求对某个消息感兴趣的应用初始化应该在系统进程注册match规则。
dbus_bus_add_match接口用于设定规则,通过这个接口可以指定:发送者,特点接口,成员,对象路径,发送目的地等规则。所以发送者在发送信号的时候需要设定某个对象下的某个接口下的某个消息。
dbus_message_new_signal接口用于新建一个信号结构体,其实不管是发送信号或则错误信息或则方法调用都是这个消息结构体,结构体头部有一个有个域指定创建的消息类型有:DBUS_MESSAGE_TYPE_SIGNAL/DBUS_MESSAG_TYPE_METHOD_CALL/DBUS_MESSAG_TYPE_ERROR/DBUS_MESSAG_TYPE_METHOD_RETURN四种。针对发送消息而言不同的是还需要调用dbus_message_set_no_reply接口设定DBUS_HEADER_FLAG_NO_REPLY_EXPECTED标志位指定不需要应答。
另外DBus里面需要指定过滤器来触发消息并通过dbus_message_is_signal接口来判断是不是自己想要的消息类型。
static void emit_signal(DBusConnection* conn)
{
	DBusMessage *message = NULL;
	dbus_bool_t nRet = FALSE;
	const char *v_STRING = "emit_signal";
	dbus_uint32_t serial = 0;

	message = dbus_message_new_signal(CLIENT_OBJ_PATH, CLIENT_INTERFACE, CLIENT_SIGNAL);
	if(message == NULL)
	{
		fprintf(stderr, "Message NULL\n");
		return;
	}
	
	nRet = dbus_message_append_args(message, DBUS_TYPE_STRING, &v_STRING, DBUS_TYPE_INVALID);

	dbus_connection_send(conn, message, &serial);
	if(FALSE == nRet)
	{
		fprintf(stderr, "(send)Out of Memeory\n");
		return;
	}
	dbus_connection_flush(conn);
	dbus_message_unref(message);
	message = NULL;

	printf("\nemit signal\n");
}


四.方法调用及应答
       DBus里面method call占据了很重要的位置,我们可以把method call当成一次函数调用,只不过这个函数在其他应用罢了。与信号发送不同在于,method call需要指定我们想要调去对象的一些信息,而不是自身的属性。简单的说就是你希望调去哪个对象,对象路径是什么,接口是什么,还有需要调用的method名称。dbus_message_new_method_call用来构建一个Method消息结构体并同样设置一些参数。
dbus_message_new_method_return用来构建一个应答method消息。
static void method_call(DBusConnection* conn)
{
	DBusMessage *message = NULL;
	dbus_bool_t nRet = FALSE;
	const char *v_STRING = "method_call";
	DBusPendingCall* Pending;
	DBusMessageIter args;
	

	message = dbus_message_new_method_call(SERVER_WELL_NAME, SERVER_OBJ_PATH, SERVER_INTERFACE, SERVER_METHOD);
	if(message == NULL)
	{
		fprintf(stderr, "Message NULL\n");
		return;
	}
	
	nRet = dbus_message_append_args (message, DBUS_TYPE_STRING, &v_STRING, DBUS_TYPE_INVALID);

	dbus_connection_send_with_reply(conn, message, &Pending, -1);
	if(FALSE == nRet)
	{
		fprintf(stderr, "(send)Out of Memeory\n");
		return;
	}
/*
	if(FALSE == dbus_pending_call_set_notify(Pending, method_call_notify, NULL, NULL))
	{
		printf("OMM\n");
	}
	dbus_connection_flush(conn);
	dbus_message_unref(message);
	message = NULL;
	

	if(TRUE == dbus_pending_call_get_completed(Pending))
	{
		printf("complete\n");
	}

	dbus_pending_call_unref(Pending);
*/
	dbus_connection_flush(conn);
	dbus_message_unref(message);
	message = NULL;

	dbus_pending_call_block(Pending);
	message = dbus_pending_call_steal_reply(Pending);
	if (NULL == message) 
	{
	  fprintf(stderr, "Reply Null\n"); 
	  exit(1); 
	}
	// free the pending message handle
	dbus_pending_call_unref(Pending);
	char* level = NULL;
	// read the parameters
	if (!dbus_message_iter_init(message, &args))
	  fprintf(stderr, "Message has no arguments!\n"); 
	else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args)) 
	  fprintf(stderr, "Argument is not int!\n"); 
	else
	  dbus_message_iter_get_basic(&args, &level);

	printf("Got Reply: %s\n", level);
	dbus_message_unref(message);
}


五.消息收发总结
      通过上面的几个步骤我们可以构建信号和method call等消息,接下来说下DBus里面消息的具体收发以及我自己在使用过程中遇到的一些问题。
1.消息发送
      消息发送常用dbus_connection_send接口,这个接口是非阻塞式的,单纯的只是把当前要发送的消息添加到系统发送队列然后返回。如果你想马上把消息发送出去可以调用dbus_connection_flush();该函数阻塞直到发送队列为空,这两个函数的结合恰好是dbus_connection_send_with_reply_and_block接口的作用。另外一个接口是dbus_connection_send_with_reply和send类似,把消息加到发送队列,不同的是可以设置超时时间,以及一个Pending用于接受消息。在等待消息的过程中,如果接受消息调用的是dbus_connection_dispatch系列接口那么首先返回的是Pending,然后是过滤器触发,最后是对象路径观察函数的触发。在这系列过程中如果有过滤器返回 DBUS_HANDLER_RESULT_HANDLED表明该消息已经被处理过了,那么消息不再继续往下面传递。

      有些时候我们有这的要求例如:我们发送消息不希望一直阻塞在发送接口而当有应答消息的时候希望系统给我们提示。DBUS也有类似的机制我们在调用dbus_connection_send_with_reply接口发送消息之后可以调用dbus_pending_call_set_notify接口为Pedning消息设定一个notify函数,当有消息应答的时候我们自己设定的函数被触发这样就达到了异步通信的目的。在多线程里面我们常常会有这样的需求,但是DBus对多线程支持的并不好。换句话说,dbus_pending_call_set_notify是非线程安全的,假如我们有一个线程调用了dbus_pending_call_set_notify接口等待应答触发,另外一个线程调用了dispatcher 分发函数把接收到的消息处理,那么第一个线程的notify永远不会被触发。在网络上查找了相关资料之后发现,dbus_pending_call_set_notify和多线程只能二者选择一个。如果要使用多线程消息发送的时候最好调用send_with_reply_and_block()发送消息,然后再设置notify函数,最后用dbus_pending_call_steal_reply()提取出消息。


2.消息接收
DBuS支持几种消息接收方式。

1. dbus_connection_read_write(conn, 0);
2. msg = dbus_connection_pop_message(conn)

     Read_write函数阻塞直到有消息或者发生错误,常常用在一些没有消息循环的地方pop函数每次从队列中取出一个消息。这种方式有一个缺点在于,它不会触发我们之前设置的一些处理handler。这不是我们希望的结果。另外一种消息接收方式为dbus_connection_dispatch它需要配合dbus_connection_get_dispatch_status一起使用,get_dispatch_status函数获取当前接收消息队列的状态如果有消息需要被分发处理就调用dispatch函数,dispatch函数会触发我们之前设定的handler。

while(1)
{
//	sleep(1);
	while(dbus_connection_read_write_dispatch(conn, -1));
}

小结:

    利用DBus作为进程间通信方式,我们经常会使用Glib或者python的binding版本,而不是直接使用DBus原始API,但是了解底层实现还是很有必要的,否则一种只见树木不见森林的感觉会一直萦绕。上面我们介绍了没有main loop的情况下一些通信方法。后面会把事件循环结合起来研究一下上层binding的一些实现。

参考文献:http://en.it-usenet.org/thread/12034/6281/

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!