Qt 学习之路 2(70):进程间通信
Qt 学习之路 2(70):进程间通信
上一章我们了解了有关进程的基本知识。我们将进程理解为相互独立的正在运行的程序。由于二者是相互独立的,就存在交互的可能性,也就是我们所说的进程间通信(Inter-Process Communication,IPC)。不过也正因此,我们的一些简单的交互方式,比如普通的信号槽机制等,并不适用于进程间的相互通信。我们说过,进程是操作系统的基本调度单元,因此,进程间交互不可避免与操作系统的实现息息相关。
Qt 提供了四种进程间通信的方式:
- 使用共享内存(shared memory)交互:这是 Qt 提供的一种各个平台均有支持的进程间交互的方式。
- TCP/IP:其基本思想就是将同一机器上面的两个进程一个当做服务器,一个当做客户端,二者通过网络协议进行交互。除了两个进程是在同一台机器上,这种交互方式与普通的 C/S 程序没有本质区别。Qt 提供了 QNetworkAccessManager 对此进行支持。
- D-Bus:freedesktop 组织开发的一种低开销、低延迟的 IPC 实现。Qt 提供了 QtDBus 模块,把信号槽机制扩展到进程级别(因此我们前面强调是“普通的”信号槽机制无法实现 IPC),使得开发者可以在一个进程中发出信号,由其它进程的槽函数响应信号。
- 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 与此类似,这里不再赘述)。程序有两个按钮,一个按钮用于加载一张图片,然后将该图片放在共享内存段;第二个按钮用于从共享内存段读取该图片并显示出来。
class QSharedMemory;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QSharedMemory *sharedMemory;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//!!! Qt5
class QSharedMemory;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QSharedMemory *sharedMemory;
};
|
头文件中,我们将MainWindow
添加一个sharedMemory
属性。这就是我们的共享内存段。接下来得实现文件中:
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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
看做是指向系统共享内存段的指针,而这个键就可以看做指针的名字。多个线程或进程使用同一个共享内存段时,该键值必须相同。接下来是两个按钮和一个标签用于界面显示,这里不再赘述。
下面来看加载图片按钮的实现:
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();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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()
复制内存段。不要忘记之前我们对共享内存段加锁,在最后需要将其解锁。
接下来是加载按钮的代码:
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);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 影音等共享用户头像,还是非常有用的。
- 大川 2014年8月20日
豆子老师,按save以后,在按Load按钮的时候出现 “Attach Error:” “,并不会加载图片,为什么呢。
豆子 2014年8月20日
看来是共享内存没有附加,不知道后面有没有错误信息?
刘 2015年1月13日
我也出现了上面同样的问题啊
“Attach Error:” “,和他是一样的问题,就是sharedMemory->errorString()输出的值为空,那个冒号后面是个空字符串SD 2016年2月21日
我觉得第二个connect 可能是这样的:
connect(loadButton, &QPushButton::clicked, [=]() {
if (sharedMemory->attach()) {
//if (!sharedMemory->isAttached()) {
//qDebug() << tr("Shared memory NOT attached!");
qDebug() << tr("Attach Error: ") <errorString();
} else {
QBuffer buffer;
QDataStream in(&buffer);
QPixmap pixmap;
sharedMemory->lock();
buffer.setData(static_cast(sharedMemory->constData()), sharedMemory->size());
buffer.open(QBuffer::ReadWrite);
in >> pixmap;
sharedMemory->unlock();
sharedMemory->detach();
picLabel->setPixmap(pixmap);
}
});Qt newbi 2016年3月16日
这个地方感觉应该是 if (!sharedMemory->isAttached())
cc 2016年3月11日
问一个问题:windwos某一个销毁了QSharedMemory实例,共享内存就会被销毁是吗,两个进程分别创建了一个QSharedMemory实例,链接到同一片共享内存上去,这时一个进程销毁了他自己的实例,另一个进程就不能访问共享内存区了吗,另一个进程的QSharedMemory没有被析构啊
sealer 2016年7月22日
楼主的代码有问题,sharedMemory->attach()返回的是错误代码,成功返回0,失败返回大于等于1的错误代码,所以即使attach成功也会报Attach Error:,不能加载共享内存中的信息
Leon 2016年8月12日
注意看Qt的帮助文档。attach()返回的是bool值,成功返回ture, 失败返回false。
Leon 2016年8月12日
我应该是因为QSharedMemory在同一个进程中,由于之前调用create()函数在系统内核中创建该了共享内存,与此同时相当于调用了一次attach(),而该函数在同一个进程中在没有detach()之前再次attach()就会失败; 但是失败并不意味着出错,此时需要再判断error() == QSharedMemory::NoError 才可以完全判断是否出错.
back 2016年12月26日
你说的是对的!在没有detach前,attach会返回失败,但是失败代码返回0,表示无错误发生!
rjosodtssp 2017年5月3日
Leon讲的不错,对于第二段代码的开始部分,如果用楼主的会输出”Attach Error: ” “”,但实际attach没有出错。应该改成:
if(sharedMemory->error()!=QSharedMemory::NoError)
qDebug() << tr("Attach Error: ") <errorString();
这样就成功了
Qt 学习之路 2(70):进程间通信的更多相关文章
- Qt 学习之路 2(11):布局管理器
Home / Qt 学习之路 2 / Qt 学习之路 2(11):布局管理器 Qt 学习之路 2(11):布局管理器 豆子 2012年9月4日 Qt 学习之路 2 70条评论 所谓 GUI 界 ...
- Qt 学习之路 2(66):访问网络(2)
Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2) 豆子 2013年10月31日 Qt 学习之路 2 27条评论 上一 ...
- Qt 学习之路 2(51):布尔表达式树模型
Qt 学习之路 2(51):布尔表达式树模型 豆子 2013年5月15日 Qt 学习之路 2 17条评论 本章将会是自定义模型的最后一部分.原本打算结束这部分内容,不过实在不忍心放弃这个示例.来自于 ...
- Qt 学习之路 2(42):QListWidget、QTreeWidget 和 QTableWidget
Qt 学习之路 2(42):QListWidget.QTreeWidget 和 QTableWidget 豆子 2013年2月5日 Qt 学习之路 2 38条评论 上一章我们了解了 model/vie ...
- Qt 学习之路 2(29):绘制设备
Qt 学习之路 2(29):绘制设备 豆子 2012年12月3日 Qt 学习之路 2 28条评论 绘图设备是继承QPainterDevice的类.QPaintDevice就是能够进行绘制的类,也就是说 ...
- 《Qt 学习之路 2》目录
<Qt 学习之路 2>目录 <Qt 学习之路 2>目录 豆子 2012年8月23日 Qt 学习之路 2 177条评论 <Qt 学习之路 2>目录 序 Qt ...
- QT学习之路--创建一个对话框
Q_OBJECT:这是一个宏,凡是定义信号槽的类都必须声明这个宏. 函数tr()全名是QObject::tr(),被他处理过的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用. 对于QT学 ...
- 转载: Qt 学习之路 2归档
Qt 学习之路 2归档 http://www.devbean.net/2012/08/qt-study-road-2-catelog/
- Qt学习之路
Qt学习之路_14(简易音乐播放器) Qt学习之路_13(简易俄罗斯方块) Qt学习之路_12(简易数据管理系统) Qt学习之路_11(简易多文档编辑器) Qt学习之路_10(Qt ...
随机推荐
- POJ1012(约瑟夫问题)
1.题目链接地址 http://poj.org/problem?id=1012 2k个人,前面k个是好人,后面k个是坏人,找一个数t,每数到第t时就去掉,使所有坏人在好人之前被杀掉. 思路:约瑟夫公式 ...
- php-fpm, nginx ,fastcgi ,php-cgi 关系粗解
首先,CGI 是干什么的? CGI 是为了保证web server传递过来的数据是标准格式.CGI 是个协议和 进程没什么关系. CGI 是http服务器于你的本机或者其他电脑上的程序交谈的一种工 ...
- python中Dict与OrderedDict
使用dict时,Key是无序的.在对dict做迭代时,我们无法确定Key的顺序. 如果要保持Key的顺序,可以用OrderedDict: from collections import Ordered ...
- Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.38/images/json: dial unix /var/run/docker.sock: conne
使用docker报如下错误信息: Got permission denied while trying to connect to the Docker daemon socket at unix:/ ...
- c语言实践 统计输入的一串正整数里面奇数和偶数的个数
怎么考虑这个问题. 首先先确定肯定是需要一个变量保存输入的数据的,我们叫它input,最后结果要的是个数,所以需要另外两个变量来保存奇数的个数和偶数的个数. int input int countJ ...
- Net-tools
一.简介 Net-tools 包含如下程序,构成了 Linux 网络的基础. arp用来操作核心的ARP(地址解析协议)的高速缓存,通常用来增加.删除一个条目以及转储ARP高速缓存. dnsdomai ...
- postfix配置积累(不断的积累)
postfix 配置 1.mail_name 默认是Postfix.在收件人信头可以查看,如果不想让别人知道你是用postfix发的,则可以改成其它名字,如:postconf -e mail_name ...
- 487C Prefix Product Sequence
传送门 题目大意 分析 因为n为质数所以i-1的逆元唯一 因此ai唯一 代码 #include<iostream> #include<cstdio> #include<c ...
- 关于Tomcat中封装请求-响应的结构的分析
在编写Servlet时,往往只重写了doGet和doPost方法,使用Tomcat通过(HttpServletRequest 和 HttpServletResponse)接口传递来的request和r ...
- JavaScript——Dom编程(2)
①.创建一个元素节点: var reference = document.createElement(element) createElement(): 按照给定的标签名创建一个新的元素节点. 方法只 ...