1.D-BUS 的内部工作方式
典型的D-BUS设置将由几个总线构成。将有一个持久的系统总线(system bus),它在引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序不能欺骗系统事件。还将有很多会话总线(session buses),这些总线当用户登录后启动,属于那个用户私有。它是用户的应用程序用来通信的一个会话总线。如果一个应用程序需要接收来自系统总线的消息,可以直接连接到系统总线 —— 不过,它可以发送的消息将是受限的。只有Linux内核、Linux桌面环境和权限较高的程序才能向系统总线写入消息,以此保障系统安全性,防止有恶意进程假冒Linux发送消息。
一旦应用程序连接到了一个总线,它们就必须通过添加匹配器(matchers)来声明它们希望收到哪种消息。匹配器为可以基于接口、对象路径和方法进行接收的消息指定一组规则。这样就使得应用程序可以集中精力去处理它们想处理的内容,以实现消息的高效路由,并保持总线上消息的预期数量,以使得不会因为这些消息导致所有应用程序的性能下降并变得很慢。
1.1对象
本质上,D-BUS是一个对等(peer-to-peer)的协议 —— 每个消息都有一个源和一个目的。这些地址被指定为对象路径。概念上,所有使用 D-BUS 的应用程序都包括一组对象,消息发送到或者发送自某一个对象,这些对象由对象路径来唯一标识。
每个对象都可以支持一个或多个接口(interfaces)。这些接口看起来类似于 Java 中 的接口或者 C++ 中的纯粹的虚类(pure virtual classes)。一个接口就是多个方法和信号的集合。dbus使用简单的命名空间字符串来表示接口,如org.freedesktop.Introspectable。
1.2消息
在 D-BUS 中有四种类型的消息:
1.方法调用(method call) 在对象上执行一个方法
2.方法返回(method reply)返回方法执行的结果
3.错误(error)调用方法产生的异常
4.信号(signal)通知指定的信号发生了,可以想象成“事件”。
要执行 D-BUS 对象的方法,您需要向对象发送一个”methodcall”消息。它将完成一些处理并返回一个”methodReply”消息或者”error”消息。信号的不同之处在于它们不返回任何内容:既没有“methodReply”消息,也没有 任何类型的”error”消息。
消息也可以有任意的参数。参数是强类型的,类型的范围是从基本的非派生类型(布尔(booleans)、 字节(bytes)、整型(integers))到高层次数据结构(字符串(strings)、数组( arrays)和字典(dictionaries))。
2.D-Bus low-level API的使用
2.1建立服务的流程
建立一个dbus连接之后 -- dbus_bus_get(),为这个连接注册监视函数--dbus_connection_set_watch_functions(),为这个连接注册超时函数--dbus_connection_set_timeout_functions(),为这个连接注册消息处理函数--dbus_connection_add_filter(),为这个dbus连接(DbusConnection)起名 -- dbus_bus_request_name(),这个名字将会成为我们在后续进行远程调用的时候的服务名,为这个连接添加匹配器dbus_bus_add_match(),将对象路径注册到总线,然后我们进入文件描述符读写就绪状态监听循环。在循环中,我们从总线上取出消息 -- dbus_watch_handle(),然后通过dbus_connection_dispatch()分发给前面通过dbus_connection_add_filter()注册的消息处理函数,并通过比对消息中的方法接口名和方法名 -- dbus_message_is_method_call(),如果一致,那么我们跳转到相应的处理中去。在相应的处理中,我们会从消息中取出远程调用的参数。并且建立起回传结果的通路 -- reply_to_method_call()。回传动作本身等同于一次不需要等待结果的远程调用。
2.2发送信号的流程:
建立一个dbus连接之后,为这个dbus连接起名,建立一个发送信号的通道,注意,在建立通道的函数中,需要我们填写该信号的接口名和信号名 -- dbus_message_new_signal()。然后我们把信号对应的相关参数压进去 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic()。然后就可以启动发送了 -- dbus_connection_send(); dbus_connection_flush。
2.3 进行一次远程调用的流程:
建立好dbus连接之后,为这dbus连接命名,申请一个远程调用通道 -- dbus_message_new_method_call(),注意,在申请远程调用通道的时候,需要填写服务器名,本次调用的接口名,和本次调用名(方法名)。压入本次调用的参数 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic(),实际上是申请了一个首地址,我们就是把我们真正要传的参数,往这个首地址里面送(送完之后一般都会判断是否内存越界了)。然后就是启动发送调用并释放发送相关的消息结构 -- dbus_connection_send_with_reply()。这个启动函数中带有一个句柄。我们马上会阻塞等待这个句柄给我们带回总线上回传的消息。当这个句柄回传消息之后,我们从消息结构中分离出参数。用dbus提供的函数提取参数的类型和参数 -- dbus_message_iter_init(); dbus_message_iter_next(); dbus_message_iter_get_arg_type(); dbus_message_iter_get_basic()。也就达成了我们进行本次远程调用的目的了。
2.4信号接收流程:
建立一个dbus连接之后,为这个dbus连接起名,为我们将要进行的消息循环添加匹配条件(就是通过信号名和信号接口名来进行匹配控制的) -- dbus_bus_add_match()。我们进入等待循环后,只需要对信号名,信号接口名进行判断就可以分别处理各种信号了。在各个处理分支上。我们可以分离出消息中的参数。对参数类型进行判断和其他的处理。
3.D-Bus low-level public API的使用理解
3.1关于DBusConnection的理解
DBusConnection代表着和总线或者另外一个应用的连接,通过它可以来来发送和接收消息,通过函数dbus_bus_get()函数,来连接到一个已经公布名称的总线,进而获取一个DBusConnection连接。DBusConnection可以看做是一个维护着接收和发送的两个消息队列,它基于SOCKET进行数据的传送。
在DBusConnection上通过DBusWatch和DBusTimeout两个对象来决定何时进行消息接收、消息发送和消息分发的处理操作,通过dbus_connection_dispatch()函数,来处理接收消息队列最顶端的一个消息,dbus_connection_dispatch()函数内部会执行已经注册过的消息处理函数(在resourcemanager程序中是msg_filter()函数)。删除接收消息队列中这个处理过的消息,然后返回。
dbus_connection_get_dispatch_status()函数指示是否队列中需要等待处理的消息。
为了达到对消息队列的异步处理,我们需要两个函数dbus_connection_set_watch_functions(), dbus_connection_set_timeout_functions() 来整合DBusConnection到我们函数自己的主循环中去(在resourcemanger程序中是bus_watch_daemon)。如果不需要异步处理的话,可以忽略对象DBusWatch, DBusTimeout以及函数dbus_connection_dispatch().然后就需要使用函数 dbus_connection_read_write_dispatch().
当使用函数dbus_connection_send()发送消息的时候,消息被添加到DBusConnection的发送队列中去,如果执行dbus_connection_flush()函数,那么进程将被阻塞,直到发送队列中的消息被通过SOCKET完全传送出去;如果不执行该函数,则会在下次主循环执行dbus_watch_handle的时候被通过SOCKET传送出去。
当连接被断开的时候,会在 interface DBUS_INTERFACE_LOCAL, path DBUS_PATH_LOCAL上接收到“Disconnected”信号,在连接被断开以前,必须保持对DBusConnection的引用。
3.2 关于DBusMessage的理解
消息DBusMessage通过DBusConnection进行发送和接收。
消息具有消息头,消息头的属性通过函数dbus_message_get_type()来获取,消息包含的消息头,指示了消息的来源 消息的目标地 方法调用或信号调用 以及其他信息,通过函数dbus_message_get_member()来获取。也可以通过更加简便的方式来获取,包括
dbus_message_is_method_call(),判断是否是方法调用
dbus_message_is_signal(),判断是否是信号消息
dbus_message_is_error()判断是否是出错消息
消息携带的参数,通过dbus_message_get_signature()来获取签名,对于简单的参数通过dbus_message_get_args()来获取,对于复杂的结构体参数,通过DBusMessageIter来获得。