【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 影音等共享用戶頭像,仍是很是有用的。

相關文章
相關標籤/搜索