当前位置:   article > 正文

Qt进程间通信_qt 进程间通信的几种方法

qt 进程间通信的几种方法

进程是操作系统的基础之一。一个进程可以认为是一个正在执行的程序。我们可以把进程当做计算机运行时的一个基础单位。关于进程的讨论已经超出了本章的范畴,现在我们假定你是了解这个概念的。

在 Qt 中,我们使用 QProcess 来表示一个进程。这个类可以允许我们的应用程序开启一个新的外部程序,并且与这个程序进行通讯。下面我们用一个非常简单的例子开始我们本章有关进程的阐述。

  1. //!!! Qt5
  2. QString program = "C:/Windows/System32/cmd.exe";
  3. QStringList arguments;
  4. arguments << "/c" << "dir" << "C:\\";
  5. QProcess *cmdProcess = new QProcess;
  6. QObject::connect(cmdProcess, &QProcess::readyRead, [=] () {
  7. QTextCodec *codec = QTextCodec::codecForName("GBK");
  8. QString dir = codec->toUnicode(cmdProcess->readAll());
  9. qDebug() << dir;
  10. });
  11. cmdProcess->start(program, arguments);

这是一段 Qt5 的程序,并且仅能运行于 Windows 平台。简单来说,这段程序通过 Qt 开启了一个新的进程,这个进程相当于执行了下面的命令:

C:\Windows\System32\cmd.exe /c dir C:\

注意,我们可以在上面的程序中找到这个命令的每一个字符。事实上,我们可以把一个进程看做执行了一段命令(在 Windows 平台就是控制台命令;在 Linux 平台(包括 Unix)则是执行一个普通的命令,比如 ls)。我们的程序相当于执行了 dir 命令,其参数是 C:\,这是由 arguments 数组决定的(至于为什么我们需要将 dir 命令作为参数传递给 cmd.exe,这是由于 Windows 平台的规定。在 Windows 中,dir 命令并不是一个独立的可执行程序,而是通过 cmd.exe 进行解释;这与 ls 在 Linux 中的地位不同,在 Linux 中,ls 就是一个可执行程序。因此如果你需要在 Linux 中执行 ls,那么 program 的值应该就是 ls )。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

上面程序的运行结果类似于:

  1. 驱动器 C 中的卷是 SYSTEM
  2. 卷的序列号是 EA62-24AB
  3. C:\ 的目录
  4. 2013/05/05 20:41 1,024 .rnd
  5. 2013/08/22 23:22 <DIR> PerfLogs
  6. 2013/10/18 07:32 <DIR> Program Files
  7. 2013/10/30 12:36 <DIR> Program Files (x86)
  8. 2013/10/31 20:30 12,906 shared.log
  9. 2013/10/18 07:33 <DIR> Users
  10. 2013/11/06 21:41 <DIR> Windows
  11. 2 个文件 13,930 字节
  12. 5 个目录 22,723,440,640 可用字节

上面的输出会根据不同机器有所不同。豆子是在 Windows 8.1 64 位机器上测试的。

为了开启进程,我们将外部程序名字(program)和程序启动参数(arguments)作为参数传给 QProcess::start() 函数。当然,你也可以使用 setProgram() 和 setArguments() 进行设置。此时,QProcess 进入 Starting 状态;当程序开始执行之后,QProcess 进入 Running 状态,并且发出 started() 信号。当进程退出时,QProcess 进入 NotRunning 状态(也是初始状态),并且发出 finished() 信号。finished() 信号以参数的形式提供进程的退出代码和退出状态。如果发送错误,QProcess 会发出 error() 信号

QProcess 允许你将一个进程当做一个顺序访问的 I/O 设备。我们可以使用 write() 函数将数据提供给进程的标准输入;使用 read()、readLine() 或者 getChar() 函数获取其标准输出。由于 QProcess 继承自 QIODevice,因此 QProcess 也可以作为 QXmlReader 的输入或者直接使用 QNetworkAccessManager 将其生成的数据上传到网络。

进程通常有两个预定义的通道:标准输出通道(stdout)和标准错误通道(stderr)。前者就是常规控制台的输出,后者则是由进程输出的错误信息。这两个通道都是独立的数据流,我们可以通过使用 setReadChannel() 函数来切换这两个通道。当进程的当前通道可用时,QProcess 会发出 readReady() 信号。当有了新的标准输出数据时,QProcess 会发出 readyReadStandardOutput() 信号;当有了新的标准错误数据时,则会发出 readyReadStandardError() 信号。我们前面的示例程序就是使用了 readReady() 信号。注意,由于我们是运行在 Windows 平台,Windows 控制台的默认编码是 GBK,为了避免出现乱码,我们必须设置文本的编码方式。

通道的术语可能会引起误会。注意,进程的输出通道对应着 QProcess 的 通道,进程的输入通道对应着 QProcess 的 通道。这是因为我们使用 QProcess“读取” 进程的输出,而我们针对 QProcess 的 “写入” 则成为进程的输入。QProcess 还可以合并标准输出和标准错误通道,使用 setProcessChannelMode() 函数设置 MergedChannels 即可实现。

另外,QProcess 还允许我们使用 setEnvironment() 为进程设置环境变量,或者使用 setWorkingDirectory() 为进程设置工作目录。

前面我们所说的信号槽机制,类似于前面我们介绍的 QNetworkAccessManager,都是异步的。与 QNetworkAccessManager 不同在于,QProcess 提供了同步函数:

  • waitForStarted():阻塞到进程开始;
  • waitForReadyRead():阻塞到可以从进程的当前读通道读取新的数据;
  • waitForBytesWritten():阻塞到数据写入进程;
  • waitForFinished():阻塞到进程结束;

注意,在主线程(调用了 QApplication::exec() 的线程)调用上面几个函数会让界面失去响应。

上面我们了解了有关进程的基本知识。我们将进程理解为相互独立的正在运行的程序。由于二者是相互独立的,就存在交互的可能性,也就是我们所说的进程间通信(Inter-Process Communication,IPC)。不过也正因此,我们的一些简单的交互方式,比如普通的信号槽机制等,并不适用于进程间的相互通信。我们说过,进程是操作系统的基本调度单元,因此,进程间交互不可避免与操作系统的实现息息相关。

Qt 提供了四种进程间通信的方式:

  1. 使用共享内存(shared memory)交互:这是 Qt 提供的一种各个平台均有支持的进程间交互的方式。
  2. TCP/IP:其基本思想就是将同一机器上面的两个进程一个当做服务器,一个当做客户端,二者通过网络协议进行交互。除了两个进程是在同一台机器上,这种交互方式与普通的 C/S 程序没有本质区别。Qt 提供了 QNetworkAccessManager 对此进行支持。
  3. D-Bus:freedesktop 组织开发的一种低开销、低延迟的 IPC 实现。Qt 提供了 QtDBus 模块,把信号槽机制扩展到进程级别(因此我们前面强调是 “普通的” 信号槽机制无法实现 IPC),使得开发者可以在一个进程中发出信号,由其它进程的槽函数响应信号。
  4. QCOP(Qt COmmunication Protocol):QCOP 是 Qt 内部的一种通信协议,用于不同的客户端之间在同一地址空间内部或者不同的进程之间的通信。目前,这种机制只用于 Qt for Embedded Linux 版本。

从上面的介绍中可以看到,通用的 IPC 实现大致只有共享内存和 TCP/IP 两种。后者我们前面已经大致介绍过(应用程序级别的 QNetworkAccessManager 或者更底层的 QTcpSocket 等);本章我们主要介绍前者。

Qt 使用 QSharedMemory 类操作共享内存段。我们可以把 QSharedMemory 看做一种指针,这种指针指向分配出来的一个共享内存段。而这个共享内存段是由底层的操作系统提供,可以供多个线程或进程使用。因此,QSharedMemory 可以看做是专供 Qt 程序访问这个共享内存段的指针。同时,QSharedMemory 还提供了单一线程或进程互斥访问某一内存区域的能力。当我们创建了 QSharedMemory 实例后,可以使用其 create() 函数请求操作系统分配一个共享内存段。如果创建成功(函数返回 true),Qt 会自动将系统分配的共享内存段连接(attach)到本进程。

前面我们说过,IPC 离不开平台特性。作为 IPC 的实现之一的共享内存也遵循这一原则。有关共享内存段,各个平台的实现也有所不同:

  • Windows:QSharedMemory 不 “拥有” 共享内存段。当使用了共享内存段的所有线程或进程中的某一个销毁了 QSharedMemory 实例,或者所有的都退出,Windows 内核会自动释放共享内存段。
  • Unix:QSharedMemory“拥有” 共享内存段。当最后一个线程或进程同共享内存分离,并且调用了 QSharedMemory 的析构函数之后,Unix 内核会将共享内存段释放。注意,这里与 Windows 不同之处在于,如果使用了共享内存段的线程或进程没有调用 QSharedMemory 的析构函数,程序将会崩溃。
  • HP-UX:每个进程只允许连接到一个共享内存段。这意味着在 HP-UX 平台,QSharedMemory 不应被多个线程使用。

下面我们通过一段经典的代码来演示共享内存的使用。这段代码修改自 Qt 自带示例程序(注意这里直接使用了 Qt5,Qt4 与此类似,这里不再赘述)。程序有两个按钮,一个按钮用于加载一张图片,然后将该图片放在共享内存段;第二个按钮用于从共享内存段读取该图片并显示出来。

  1. //!!! Qt5
  2. class QSharedMemory;
  3. class MainWindow : public QMainWindow
  4. {
  5. Q_OBJECT
  6. public:
  7. MainWindow(QWidget *parent = 0);
  8. ~MainWindow();
  9. private:
  10. QSharedMemory *sharedMemory;
  11. };

头文件中,我们将 MainWindow 添加一个 sharedMemory 属性。这就是我们的共享内存段。接下来得实现文件中:

  1. const char *KEY_SHARED_MEMORY = "Shared";
  2. MainWindow::MainWindow(QWidget *parent)
  3. : QMainWindow(parent),
  4. sharedMemory(new QSharedMemory(KEY_SHARED_MEMORY, this))
  5. {
  6. QWidget *mainWidget = new QWidget(this);
  7. QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget);
  8. setCentralWidget(mainWidget);
  9. QPushButton *saveButton = new QPushButton(tr("Save"), this);
  10. mainLayout->addWidget(saveButton);
  11. QLabel *picLabel = new QLabel(this);
  12. mainLayout->addWidget(picLabel);
  13. QPushButton *loadButton = new QPushButton(tr("Load"), this);
  14. mainLayout->addWidget(loadButton);

构造函数初始化列表中我们将 sharedMemory 成员变量进行初始化。注意我们给出一个键(Key),前面说过,我们可以把 QSharedMemory 看做是指向系统共享内存段的指针,而这个键就可以看做指针的名字。多个线程或进程使用同一个共享内存段时,该键值必须相同。接下来是两个按钮和一个标签用于界面显示,这里不再赘述。

下面来看加载图片按钮的实现:

  1. connect(saveButton, &QPushButton::clicked, [=]() {
  2. if (sharedMemory->isAttached()) {
  3. sharedMemory->detach();
  4. }
  5. QString filename = QFileDialog::getOpenFileName(this);
  6. QPixmap pixmap(filename);
  7. picLabel->setPixmap(pixmap);
  8. QBuffer buffer;
  9. QDataStream out(&buffer);
  10. buffer.open(QBuffer::ReadWrite);
  11. out << pixmap;
  12. int size = buffer.size();
  13. if (!sharedMemory->create(size)) {
  14. qDebug() << tr("Create Error: ") << sharedMemory->errorString();
  15. } else {
  16. sharedMemory->lock();
  17. char *to = static_cast<char *>(sharedMemory->data());
  18. const char *from = buffer.data().constData();
  19. memcpy(to, from, qMin(size, sharedMemory->size()));
  20. sharedMemory->unlock();
  21. }
  22. });

点击加载按钮之后,如果 sharedMemory 已经与某个线程或进程连接,则将其断开(因为我们就要向共享内存段写入内容了)。然后使用 QFileDialog 选择一张图片,利用 QBuffer 将图片数据作为 char * 格式。在即将写入共享内存之前,我们需要请求系统创建一个共享内存段(QSharedMemory::create() 函数),创建成功则开始写入共享内存段。需要注意的是,在读取或写入共享内存时,都需要使用 QSharedMemory::lock() 函数对共享内存段加锁。共享内存段就是一段普通内存,所以我们使用 C 语言标准函数 memcpy() 复制内存段。不要忘记之前我们对共享内存段加锁,在最后需要将其解锁。

接下来是加载按钮的代码:

  1. connect(loadButton, &QPushButton::clicked, [=]() {
  2. if (!sharedMemory->attach()) {
  3. qDebug() << tr("Attach Error: ") << sharedMemory->errorString();
  4. } else {
  5. QBuffer buffer;
  6. QDataStream in(&buffer);
  7. QPixmap pixmap;
  8. sharedMemory->lock();
  9. buffer.setData(static_cast<const char *>(sharedMemory->constData()), sharedMemory->size());
  10. buffer.open(QBuffer::ReadWrite);
  11. in >> pixmap;
  12. sharedMemory->unlock();
  13. sharedMemory->detach();
  14. picLabel->setPixmap(pixmap);
  15. }
  16. });

如果共享内存段已经连接,还是用 QBuffer 读取二进制数据,然后生成图片。注意我们在操作共享内存段时还是要先加锁再解锁。最后在读取完毕后,将共享内存段断开连接。

注意,如果某个共享内存段不是由 Qt 创建的,我们也是可以在 Qt 应用程序中使用。不过这种情况下我们必须使用 QSharedMemory::setNativeKey() 来设置共享内存段。使用原始键(native key)时,QSharedMemory::lock() 函数就会失效,我们必须自己保护共享内存段不会在多线程或进程访问时出现问题。

IPC 使用共享内存通信是一个很常用的开发方法。多个进程间得通信要比多线程间得通信少一些,不过在某一族的应用情形下,比如 QQ 与 QQ 音乐、QQ 影音等共享用户头像,还是非常有用的。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/184790
推荐阅读
相关标签
  

闽ICP备14008679号