#(70):進程間通信
上一章我們了解了有關進程的基本知識。我們將進程理解為相互獨立的正在運行的程序。由于二者是相互獨立的,就存在交互的可能性,也就是我們所說的進程間通信(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 與此類似,這里不再贅述)。程序有兩個按鈕,一個按鈕用于加載一張圖片,然后將該圖片放在共享內存段;第二個按鈕用于從共享內存段讀取該圖片并顯示出來。
~~~
//!!! 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`看做是指向系統共享內存段的指針,而這個鍵就可以看做指針的名字。多個線程或進程使用同一個共享內存段時,該鍵值必須相同。接下來是兩個按鈕和一個標簽用于界面顯示,這里不再贅述。
下面來看加載圖片按鈕的實現:
~~~
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 影音等共享用戶頭像,還是非常有用的。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)