|
博士、大学讲师
2004 年 4 月
Qt 作为一种跨平台的基于 C++ 的 GUI 系统,能够提供给用户构造图形用户界面的强大功能。自从 1996 年 Qt 被 Trolltech 公司发布以来,该系统成为世界上很多成功的图形用户应用所使用的主要系统。更为重要的是,Linux 操作系统的桌面环境系统 KDE 也是基于 Qt 构造的。目前,Qt 已经提供了对包括 MS/Windows、Unix/X11 和嵌入式平台的支持,得到了越来越广泛的应用。
在 Qt 系统中,不仅有着构造完善的系统结构,而且为了满足用户对编写图形用户界面应用的种种需求,它还创建了许多新的系统机制,其中 Qt 所特有的内部进程通信机制尤其值得一提。 本文分析了基于 QT 的应用进程之间通信常用的三种机制:QCOP 协议,Signal-Slot 机制和 FIFO 机制。给出了各自的使用方法,并指出了各自的使用场合。
1、 QCOP协议
QCOP 是 Qt 内部的一种通信协议,这种协议用于不同的客户之间在同一地址空间内部或者不同的进程之间的通信。目前,这种机制还只在 Qt 的嵌入式版本中提供。
为实现这种通信机制,Qt 中包括了由 QObject 类继承而来的 QCopChannel 类,该类提供了诸如 send()、isRegistered() 等静态函数,它们可以在脱离对象的情况下使用。为了在 channel 中接收通信数据,用户需要构造一个 QCopChannel 的子类并提供 receive() 函数的重载函数,或者利用 connect() 函数与接收到的信号相联系。
值得一提的是,在 Qt 系统中,只提供了 QCOP 协议机制和用于接收消息的类,而如何发送消息则没有提供相应的类供用户使用。
在基于 Qt 的桌面系统 Qtopia(QPE)中,则提供了相应的发送类:QCopEnvelope。用户可以通过该类利用 channel 向其他进程发送消息。该类将通过 QCopChannel 发送 QCop 消息的过程进行了封装,用户只需要调用该类中的相关函数就可以方便地实现进程之间的通信过程。一方面,QCop 消息的发送要利用 QCopEnvelope 类,另一方面,接收消息则是通过与一个 QCopChannel 相关联。
在发送消息时,将利用如下的协议机制:
QCopEnvelope e(channelname, messagename);
对于需要携带参数的消息,必须使用"<<()"运算符将参数添加到envelope中。
e << parameter1 << parameter2 << ...;
对于不带参数的消息,只需要利用:
QCopEnvelope e(channelname, messagename);
在Qtopia中,所有的channels名都以"QPE/"开始,而messagename则是一个函数的标识符。
在接收消息时,通常只需要利用在应用程序中预先定义好的QPE/Application/{appname}管道,当然,也可以根据需要自己定义管道,并将其与一个slot函数相关联:
myChannel = new QCopChannel( "QPE/FooBar", this );
connect( myChannel, SIGNAL(received(const QCString &, const QByteArray &)),
this, SLOT(fooBarMessage( const QCString &, const QByteArray &)) );
下面将具体的通信过程举例如下:
在需要接收消息的类(如Window1)中定义管道:
QCopChannel *doChannel = new QCopChannel("QPE/Do", this);
connect(doChannel, SIGNAL(received(const QCString &, const QByteArray &)),
this, SLOT(doMessage(const QCString &, const QByteArray &)));
同时,需要在该类中定义相应的消息处理函数doMessage,
void Window1::doMessage(const QCString &msg, const QByteArray &args)
{
QDataStream stream(args, IO_ReadOnly);
if(msg == "Message1(QString)")
{
QString text;
stream >> text;
button->setText(text);
}
else if(msg == "Message2()")
{
close();
}
}
其中的Message1(QString)和Message2(QString)都是用户自己定义的消息,该函数中分别对这些消息进行了相应的处理。在该例中当收到带有参数的Message1消息时,将该字符串参数stream显示在按钮button上;当收到Message2消息时,将执行关闭Window1窗口的动作,当然用户可以根据需要自行编写相应的处理过程。
另一方面,在类Class2中需要发出消息的函数function中利用QCopEnvelope发送消息:
void Class2::function()
{ QCopEnvelope e("QPE/Do", "Message1(QString)");
e << param; }
这里发出了Message1消息,并将需要携带的参数param发送到管道中。
通过这样的过程,用户可以很方便地实现不同对象、不同进程之间通信过程,而且可以根据需要在通信过程中任意传递参数。
2、 信号-槽(Signal-Slot)机制
在Qt中,有一种用于对象之间的通信:信号-槽机制,这种机制是Qt的核心机制,也是它区别于其他GUI工具的最主要的特征。在大多数GUI工具中,通常为可能触发的每种行为定义一个回调函数,这个回调函数是一个指向函数的指针。在Qt中,信号-槽机制取代了这种繁杂的函数指针,能够实现同样的功能。信号-槽机制可以携带任意类型、任意数量的参数,而且完全是安全的,不会引起系统的崩溃。
所有由QObject类继承而来的类,或者是它的一个子类,都可以包括信号-槽机制。信号通常是当对象改变他们的状态时发出的,这就是一个对象在需要与其他对象通信时所需要做的一切,它并不知道是否有其他对象在另一端接收该信号。从这个意义上来说,这种机制实现了真正的信息封装,确保了对象可以被当作一个独立的软件构件来使用。
而槽可以被用于接收信号,它们通常是类中的成员函数。一个槽并不知晓是否有一个信号与自己相联系,同样,包含有槽函数的对象也对通信机制一无所知,它们也可以作为一个独立的软件构件。
用户可以按照需要将许多信号与一个单独的槽函数相联系,一个信号也可以按需要被联系到很多不同的槽函数。甚至还可以将一个信号直接与另一个信号相联系,这样当第一个信号被发出时立刻发出第二个信号。
这样,信号-槽相结合就产生了一种功能强大的编程机制。
例如:
button = new QAction(tr("button"), QIconSet(QPixmap("button.png")), 0, 0, this);
connect(button, SIGNAL(activated()), this, SLOT(slotButton()));
程序中定义了一个按钮,并利用connect()函数将该按钮button的activated()信号与slotButton()函数相关联,当用户触发按钮时,就会执行相应的槽函数。当然,这里的信号是QAction类中预先定义好的信号,用户在使用该机制时,可以根据需要自行定义信号,同时在适当的时候利用emit语句发出该信号。另外,在信号和相应的槽函数之间还可以传递任意参数,如:
emit signal(parameter);
3、 FIFO机制
当然,除了 Qt 内部所特有的通信机制之外,一般操作系统中常用的进程间通信机制同样可以用于 Qt 系统内部不同进程之间的通信。如消息队列、共享内存、信号量、管道等机制,其中有些机制,如信号量,在 Qt 中重新进行了封装;有些机制则可以直接调用操作系统的系统调用来实现。这里,有名管道是一种简单实用的通信机制,用户在对Qt内部机制
不甚了解的情况下,同样可以使用这种方法实现对象进程之间的通信。下面就对利用这种机制实现Qt内部进程之间的通信过程进行介绍。
首先,需要创建 FIFO,这个过程类似于创建文件,在系统中可以利用 mkfifo 命令来创建,这样就可以用 open 函数打开它,同时,一般的文件 I/O函数(close、read、write)都可以用于 FIFO。
在基于 Qt 的应用中,有很多应用采用了一种客户机-服务器模式,这时就可以利用 FIFO 在客户机和服务器之间传递数据。例如,有一个服务器,它负责接收底层程序发来的消息,同时,它与很多客户机有关,服务器需要将收到的不同消息发送到不同的客户机,而每个客户机也有请求需要发给服务器,进而发给底层程序。
下面是服务器端的程序示例:(架设已有客户端进程为读而打开/dev/fifoclient1和/dev/fifoclient1)
fd = open("/dev/fifoserver", O_NONBLOCK|O_RDONLY);
file = fdopen(fd, "r");
ret = fgets(buf, MAX_LINE, file );
if(buf[0] == '0')
{
QFile fd_file("/dev/fifoclient1");
QString temp(buf);
if(fd_file.open(IO_WriteOnly|IO_Append)) {
QTextStream t(&fd_file);
t<< temp;
fd_file.close();
}
else if(buf[0] == '1')
{
QFile fd_file("/dev/fifoclient2");
QString temp(buf);
if(fd_file.open(IO_WriteOnly|IO_Append)) {
QTextStream t(&fd_file);
t<< temp;
fd_file.close();
}
……
在该程序中,服务器接收底层发来的信息(这里假设也是由 FIFO 管道传来),然后根据收到的信息内容,如第一个字节的内容,将信息发到不同客户端的管道中,实现对信息的正确分发。
客户端程序示例如下:(假设服务器端已经为读而打开 /dev/fifo 管道)
QFile out_file("/dev/fifo");
if(out_file.open(IO_WriteOnly|IO_Append)) {
QTextStream t(&out_file);
t << text << "\n"; }
当任意一个客户端需要向服务器发送消息时,就可以通过 /dev/fifo 这个公共的管道发出。
通过这种方式,同样可以实现GUI内部不同进程或应用之间的通信过程,但是,当客户端数量较多时,这种方法就显示出了一定的局限性,整个通信过程布局变得过于繁杂,管道越来越多使得出错的可能性也越来越大。因此,利用 FIFO 实现 Qt 中上述客户端和服务器端的通信过程,更适用于客户端应用较少时。
参考资料
Qt 官方文档: http://doc.trolltech.com/3.2/index.html
Qt 和 Qtopia 源代码:QCopChannel, QCopEnvelope,QAction.
Advanced Programming in the UNIX Environment, W. Richard Stevens.
关于作者
续欣:博士、大学讲师,目前从事无线网络拥塞控制方面的研究,以及嵌入式GUI的开发。联系方式 [email protected] |
|