使用D-Bus信号做异步操作
- D-Bus信号的属性
- 在XML文件中声明信号
- 从GObject对象中发射信号
- 在客户端捕获信号
- 跟踪D-Bus信号
D-Bus信号的属性:
通过D-Bus实现远程函数调用(RPC)只是D-Bus的一半功能,另外D-Bus也支持广播通信,也称之为异步通信。这种异步通信机制在D-Bus中被称为信号。当你需要把一个状态的变化给通知很多程序时,这种机制是非常有用的。比如说:系统马上要关机;网络已经断开,这些情况下,接收程序不必要持续轮询这种状态,而由系统发广播通知就行了。
不过,信号也不能解决所有问题:如果接收程序并没有能力去快速地处理发给它的D-Bus消息,这个信号可能会丢失!可能还有其它的某些问题,这些问题也是其它RPC机制所共同面临的问题,并非D-Bus的特性。正因为此,如果你的程序需要格外的稳定,你需要想办法去解决这个问题。一种方法是:时不时地检查你关系的状态,不过不能太频繁了,也不能次数太少,需要仔细权衡。
D-Bus的信号可以携带一些信息。每个信号有它自己的名字(在XML中指定)和参数。信号的参数列表实际上仅仅说明在信号中传递的信息列表,不要和method的参数混淆。
信号无“返回值”,就是说当D-Bus信号发送出去后,是接收不到返回的或者就不希望接收返回值。因此,如果你需要确认信号是否发送成功,你要自己想办法了。D-Bus信号和网络上的数据报(datagram)很类似,比如UDP。
D-Bus的很多语言绑定都试图把D-Bus的信号机制映射到目标语言中,由于Glib已经对signal有很好的支持,因此这种映射就变得水到渠成了。因此下面的例子中,你的客户端的代码将注册Glib信号,并且在回调函数中处理信号(有个特定的函数用来注册信号:dbus_g_proxy_connect_signal)。
在XML接口文件中声明信号:
下面我们将对前一个例子做些扩展:增加两个阈值 (最小值和最大值),并且当设置操作越界时,对象将发射信号。另外,当对象的值发生改变时,对象也发射信号。
定义4个信号:
<node>
<interface name="org.maemo.Value">
<!-- ... Listing cut for brevity ... -->
<!-- Signal (D-Bus) definitions -->
<!-- NOTE: The current version of dbus-bindings-tool doesn't
actually enforce the signal arguments _at_all_. Signals need
to be declared in order to be passed through the bus itself,
but otherwise no checks are done! For example, you could
leave the signal arguments unspecified completely, and the
code would still work. -->
<!-- Signals to tell interested clients about state change.
We send a string parameter with them. They never can have
arguments with direction=in.
// 对象的信号,主要是告诉对该信号感兴趣的客户端:对象的状态发生了改变了,我们发送一个字符串通知出去,因此这里必须是出口参数-->
<signal name="changed_value1">
<arg type="s" name="change_source_name" direction="out"/>
</signal>
<signal name="changed_value2">
<arg type="s" name="change_source_name" direction="out"/>
</signal>
<!-- Signals to tell interested clients that values are outside
the internally configured range (thresholds). -->
<signal name="outofrange_value1">
<arg type="s" name="outofrange_source_name" direction="out"/>
</signal>
<signal name="outofrange_value2">
<arg type="s" name="outofrange_source_name" direction="out"/>
</signal>
</interface>
</node>
如果你使用dbus-binding-tool工具来生成代码的话,信号的定义是要的,但是这个工具目前对于信号的参数列表支持的还不够好,实际上,这个工具会忽略你定义的参数,因此你要手动去写实现信号动作的很多代码。将来,这个工具可能会支持信号参数列表,不过目前我们只有自己干了!
对象发射信号:
因此,我们可以很容易修改信号的名字,我们把它们定义在一个头文件中,并且在client侧和server侧使用同一个头文件:
/* Symbolic constants for the signal names to use with GLib.
These need to map into the D-Bus signal names. */
#define SIGNAL_CHANGED_VALUE1 "changed_value1"
#define SIGNAL_CHANGED_VALUE2 "changed_value2"
#define SIGNAL_OUTOFRANGE_VALUE1 "outofrange_value1"
#define SIGNAL_OUTOFRANGE_VALUE2 "outofrange_value2"
在GObject可以发射Glib信号之前,这个信号需要被定义和创建。你最好在类构造函数中做这件事,因为信号类型只需要创建一次:
/**
* Define enumerations for the different signals that we can generate
* (so that we can refer to them within the signals-array [below]
* using symbolic names). These are not the same as the signal name
* strings.
*
* NOTE: E_SIGNAL_COUNT is NOT a signal enum. We use it as a
* convenient constant giving the number of signals defined so
* far. It needs to be listed last.
*/
typedef enum {
E_SIGNAL_CHANGED_VALUE1,
E_SIGNAL_CHANGED_VALUE2,
E_SIGNAL_OUTOFRANGE_VALUE1,
E_SIGNAL_OUTOFRANGE_VALUE2,
E_SIGNAL_COUNT
} ValueSignalNumber;
/*... Listing cut for brevity ...*/
typedef struct {
/* The parent class state. */
GObjectClass parent;
/* The minimum number under which values will cause signals to be
emitted. */
gint thresholdMin;
/* The maximum number over which values will cause signals to be
emitted. */
gint thresholdMax;
/* Signals created for this class. */
guint signals[E_SIGNAL_COUNT];
} ValueObjectClass;
/*... Listing cut for brevity ...*/
/**
* Per class initializer
*
* Sets up the thresholds (-100 .. 100), creates the signals that we
* can emit from any object of this class and finally registers the
* type into the GLib/D-Bus wrapper so that it may add its own magic.
*/
static void value_object_class_init(ValueObjectClass* klass) {
/* Since all signals have the same prototype (each will get one
string as a parameter), we create them in a loop below. The only
difference between them is the index into the klass->signals
array, and the signal name.
Since the index goes from 0 to E_SIGNAL_COUNT-1, we just specify
the signal names into an array and iterate over it.
Note that the order here must correspond to the order of the
enumerations before. */
const gchar* signalNames[E_SIGNAL_COUNT] = {
SIGNAL_CHANGED_VALUE1,
SIGNAL_CHANGED_VALUE2,
SIGNAL_OUTOFRANGE_VALUE1,
SIGNAL_OUTOFRANGE_VALUE2 };
/* Loop variable */
int i;
dbg("Called");
g_assert(klass != NULL);
/* Setup sane minimums and maximums for the thresholds. There is no
way to change these afterwards (currently), so you can consider
them as constants. */
klass->thresholdMin = -100;
klass->thresholdMax = 100;
dbg("Creating signals");
/* Create the signals in one loop, since they all are similar
(except for the names). */
for (i = 0; i < E_SIGNAL_COUNT; i++) {
guint signalId;
/* Most of the time you will encounter the following code without
comments. This is why all the parameters are documented
directly below. */
signalId =
g_signal_new(signalNames[i], /* str name of the signal */
/* GType to which signal is bound to */
G_OBJECT_CLASS_TYPE(klass),
/* Combination of GSignalFlags which tell the
signal dispatch machinery how and when to
dispatch this signal. The most common is the
G_SIGNAL_RUN_LAST specification. */
G_SIGNAL_RUN_LAST,
/* Offset into the class structure for the type
function pointer. Since we're implementing a
simple class/type, we'll leave this at zero. */
0,
/* GSignalAccumulator to use. We don't need one. */
NULL,
/* User-data to pass to the accumulator. */
NULL,
/* Function to use to marshal the signal data into
the parameters of the signal call. Luckily for
us, GLib (GCClosure) already defines just the
function that we want for a signal handler that
we don't expect any return values (void) and
one that will accept one string as parameter
(besides the instance pointer and pointer to
user-data).
If no such function would exist, you would need
to create a new one (by using glib-genmarshal
tool). */
g_cclosure_marshal_VOID__STRING,
/* Return GType of the return value. The handler
does not return anything, so we use G_TYPE_NONE
to mark that. */
G_TYPE_NONE,
/* Number of parameter GTypes to follow. */
1,
/* GType(s) of the parameters. We only have one. */
G_TYPE_STRING);
/* Store the signal Id into the class state, so that we can use
it later. */
klass->signals[i] = signalId;
/* Proceed with the next signal creation. */
}
/* All signals created. */
dbg("Binding to GLib/D-Bus");
/*... Listing cut for brevity ...*/
}
信号类型是保存在类结构中的,因此很容易引用。类构造函数设置阈值的上下限。
发射信号是比较容易的,为了减少重复代码,我们写一个工具函数,专门来发射信号:
/**
* Utility helper to emit a signal given with internal enumeration and
* the passed string as the signal data.
*
* Used in the setter functions below.
*/
static void value_object_emitSignal(ValueObject* obj,
ValueSignalNumber num,
const gchar* message) {
/* In order to access the signal identifiers, we need to get a hold
of the class structure first.
//由于信号是定义在类结构中的,而不是对象结构中的,为了能访问信号,我们首先需要取得类结构 */
ValueObjectClass* klass = VALUE_OBJECT_GET_CLASS(obj);
/* Check that the given num is valid (abort if not).
Given that this file is the module actually using this utility,
you can consider this check superfluous (but useful for
development work). */
g_assert((num < E_SIGNAL_COUNT) && (num >= 0));
dbg("Emitting signal id %d, with message '%s'", num, message);
/* This is the simplest way of emitting signals. */
g_signal_emit(/* Instance of the object that is generating this
signal. This will be passed as the first parameter
to the signal handler (eventually). But obviously
when speaking about D-Bus, a signal caught on the
other side of D-Bus will be first processed by
the GLib-wrappers (the object proxy) and only then
processed by the signal handler. */
obj,//这个是对象实例
/* Signal id for the signal to generate. These are
stored inside the class state structure. */
klass->signals[num],
/* Detail of signal. Since we are not using detailed
signals, we leave this at zero (default). */
0,
/* Data to marshal into the signal. In our case it's
just one string. */
message);
/* g_signal_emit returns void, so we cannot check for success. */
/* Done emitting signal. */
}
再写个检查阈值的工具函数:
/**
* Utility to check the given integer against the thresholds.
* Will return TRUE if thresholds are not exceeded, FALSE otherwise.
*
* Used in the setter functions below.
*/
static gboolean value_object_thresholdsOk(ValueObject* obj,
gint value) {
/* Thresholds are in class state data, get access to it */
ValueObjectClass* klass = VALUE_OBJECT_GET_CLASS(obj);
return ((value >= klass->thresholdMin) &&
(value <= klass->thresholdMax));
}
下面利用上面的两个工具函数修改值,并发射信号:
/**
* Function that gets called when someone tries to execute "setvalue1"
* over the D-Bus. (Actually the marshalling code from the stubs gets
* executed first, but they will eventually execute this function.)
*/
/* 这个函数是在server侧实现的,1:当client侧的异步调用发出后,会把消息发送给D-Bus,2:D-Bus进而会调用这个server侧的函数;3:这个函数发射信号,发射给client侧的接收者 */
gboolean value_object_setvalue1(ValueObject* obj, gint valueIn,
GError** error) {
dbg("Called (valueIn=%d)", valueIn);
g_assert(obj != NULL);
/* Compare the current value against old one. If they're the same,
we don't need to do anything (except return success). */
if (obj->value1 != valueIn) {
/* Change the value. */
obj->value1 = valueIn