【Qt笔记】进程间通讯

上一章咱们了解了有关进程的基本知识。咱们将进程理解为相互独立的正在运行的程序。因为两者是相互独立的,就存在交互的可能性,也就是咱们所说的进程间通讯(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 的实现之一的共享内存也遵循这一原则。有关共享内存段,各个平台的实现也有所不一样:this

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

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

//!!! Qt5

class QSharedMemory;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    QSharedMemory *sharedMemory;
};

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

const char *KEY_SHARED_MEMORY = "Shared";

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      sharedMemory(new QSharedMemory(KEY_SHARED_MEMORY, this))
{
    QWidget *mainWidget = new QWidget(this);
    QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget);
    setCentralWidget(mainWidget);

    QPushButton *saveButton = new QPushButton(tr("Save"), this);
    mainLayout->addWidget(saveButton);
    QLabel *picLabel = new QLabel(this);
    mainLayout->addWidget(picLabel);
    QPushButton *loadButton = new QPushButton(tr("Load"), this);
    mainLayout->addWidget(loadButton);

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

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

connect(saveButton, &QPushButton::clicked, [=]() {
        if (sharedMemory->isAttached()) {
            sharedMemory->detach();
        }
        QString filename = QFileDialog::getOpenFileName(this);
        QPixmap pixmap(filename);
        picLabel->setPixmap(pixmap);

        QBuffer buffer;
        QDataStream out(&buffer);
        buffer.open(QBuffer::ReadWrite);
        out << pixmap;

        int size = buffer.size();
        if (!sharedMemory->create(size)) {
            qDebug() << tr("Create Error: ") << sharedMemory->errorString();
        } else {
            sharedMemory->lock();
            char *to = static_cast<char *>(sharedMemory->data());
            const char *from = buffer.data().constData();
            memcpy(to, from, qMin(size, sharedMemory->size()));
            sharedMemory->unlock();
        }
    });

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

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

connect(loadButton, &QPushButton::clicked, [=]() {
        if (!sharedMemory->attach()) {
            qDebug() << tr("Attach Error: ") << sharedMemory->errorString();
        } else {
            QBuffer buffer;
            QDataStream in(&buffer);
            QPixmap pixmap;
            sharedMemory->lock();
            buffer.setData(static_cast<const char *>(sharedMemory->constData()), sharedMemory->size());
            buffer.open(QBuffer::ReadWrite);
            in >> pixmap;
            sharedMemory->unlock();
            sharedMemory->detach();
            picLabel->setPixmap(pixmap);
        }
    });

若是共享内存段已经链接,仍是用QBuffer读取二进制数据,而后生成图片。注意咱们在操做共享内存段时仍是要先加锁再解锁。最后在读取完毕后,将共享内存段断开链接。

注意,若是某个共享内存段不是由 Qt 建立的,咱们也是能够在 Qt 应用程序中使用。不过这种状况下咱们必须使用QSharedMemory::setNativeKey()来设置共享内存段。使用原始键(native key)时,QSharedMemory::lock()函数就会失效,咱们必须本身保护共享内存段不会在多线程或进程访问时出现问题。

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

相关文章
相关标签/搜索