67.QT-QSharedMemory
1.QSharedMemory介绍
QSharedMemory提供了多个线程和进程对共享内存段的访问。它还提供了一种方法,让单个线程或进程锁定内存以进行独占访问。
当使用这个类时,请注意以下平台差异:
- Windows: QSharedMemory不“拥有”共享内存段。当有QSharedMemory实例附加到特定共享内存段的所有线程或进程销毁了它们的QSharedMemory实例或者退出了,Windows内核会自动释放共享内存段。
- Unix: QSharedMemory“拥有”共享内存段。当最后一个线程或进程将一个QSharedMemory实例附加到一个特定的共享内存段时,通过销毁它的QSharedMemory实例从这个段中分离出来,Unix内核释放这个共享内存段。但是如果最后一个线程或进程在没有运行QSharedMemory析构函数的情况下崩溃了(未释放),共享内存段会在崩溃时幸存下来。
在对共享内存进行读写操作之前,记得使用lock()锁定共享内存,并且记得在操作完成后使用unlock()释放锁。
它的构造函数如下所示:
QSharedMemory(const QString &key, QObject *parent = nullptr)通过指定的parent和key,构造一个共享内存对象。由于key已经被设置,所以不需要再调用setKey()或setNativeKey()指定要访问的内存块了,create()和attach()就可以被调用了。
其它常用函数如下所示:
QString QSharedMemory::key() const //返回被setKey()所赋值的key如果未设置key则返回空字符串,如果应用程序是和非Qt平台的,则需要使用nativeKey()访问 bool QSharedMemory::lock() //用来锁定共享内存的互斥值,锁住成功则返回true. 若另一个进程已经锁住了共享内存段,本函数将会阻塞直到锁被另一个进程释放。到那时,本函数才会获得锁并返回true. 如果本函数返回false,那就说明你已经忽略了一个由create()或attach()返回的false,而其原因可能是由于某个系统错误而导致setNativeKey()或QSystemSemaphore::acquire()失败。 bool QSharedMemory::unlock() //释放共享内存段上的锁并返回true,如果共享内存段没有被lock,或者如果锁被其他进程持有,本函数什么都不会做而只是返回false. bool QSharedMemory::create(int size, AccessMode mode=ReadWrite) //创建一个大小为size个字节的共享内存段, 然后用给定的访问模式mode附着到共享内存上。Mode取值有以下几种: QSharedMemory::ReadOnly : 共享内存段是只读的。不允许写入共享内存段。尝试写入使用ReadOnly创建的共享内存段会导致程序中止。 QSharedMemory::ReadWrite : 允许对共享内存段进行读写操作。 bool QSharedMemory::attach(AccessMode mode = ReadWrite) //尝试将进程附加到由传递给构造函数或调用setKey()或setNativeKey()的键标识的共享内存段上。默认访问模式为“ReadWrite”。也可以是ReadOnly。如果附加操作成功,则返回true。如果返回false,则调用error()或者errorString()来确定发生了哪个错误。在附加共享内存段成功之后,则可以通过调用data()来获得一个指向共享内存的指针。 bool QSharedMemory::isAttached() const //如果该进程附加到共享内存段,则返回true。 bool QSharedMemory::detach() //将进程从共享内存段中分离。如果这是连接到共享内存段的最后一个进程,那么共享内存段将被系统释放,也就是说,内容将被销毁。如果函数分离了共享内存段,则返回true。如果它返回false,通常意味着该段没有连接,或者被另一个进程锁定。 void * QSharedMemory::data() //如果附加了共享内存段,则返回指向共享内存段内容的指针。否则返回null。在对共享内存进行读写操作之前,记得使用lock()锁定共享内存,并且记得在操作完成后使用unlock()释放锁。2.QSharedMemory示例
界面如下所示(左边写入字符串、右边实时更新):
写内存widget-示例代码如下所示:
#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QTextEdit> #include <QByteArray> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget), sharememory("shareTest", this) { ui->setupUi(this); } Widget::~Widget() { delete ui; } void Widget::on_textEdit_textChanged() { if(sharememory.isAttached()) { sharememory.detach(); } QByteArray arr = ui->textEdit->toPlainText().toLocal8Bit(); int len = arr.length(); qDebug()<<ui->textEdit->toPlainText()<<len; if(!sharememory.create(len)) { qDebug() << sharememory.errorString(); return ; } sharememory.lock(); char *dest = reinterpret_cast<char *>(sharememory.data()); for (int i = 0; i < len; i++) { dest[i] = arr[i]; } sharememory.unlock(); }读内存widget-示例代码如下所示:
#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QTextEdit> #include <QTimer> #include <QByteArray> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget), sharememory("shareTest", this) { ui->setupUi(this); QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); timer->start(200); } void Widget::timeout() { if (!sharememory.isAttached()) { if (!sharememory.attach()) { qDebug() << sharememory.errorString(); } else { sharememory.lock(); QByteArray arr((char*)sharememory.data(), sharememory.size()); ui->textEdit->setPlainText(QString(arr)); sharememory.unlock(); sharememory.detach(); } } } Widget::~Widget() { delete ui; }3.Linux下查看共享内存
1、查看共享内存,使用命令:ipcs -m
2、删除共享内存,使用命令:ipcrm -m [shmid]
如下图所示:
1、key:共享内存的key
2、shmid:共享内存的编号
3、owner:共享内存的创建用户
4、perms:共享内存的权限
5、bytes:共享内存的大小
6、nattch:连接到共享内存的进程数
7、status:共享内存的状态
4.QSharedMemory与其他语言进程通信
如果使用QSharedMemory与非QT程序通信,就没法使用setKey()来设置共享内存名称了,需要使用setNativeKey()设置名称和nativeKey()访问。
注意:使用setNativeKey()后,QSharedMemory的lock()和unlock()函数也会失效。
接下来我们来实现一个主进程Master示例来模拟非Qt的程序:
并且需要和子进程维护协议如下所示:
4.1 主进程发送区
发送区用于主进程下发协议用的,当主进程填入一个完整的json文本给子进程后,子进程接收到就会去处理json文本,并把发送区buff[0]=’\0’进行清空处理。
如果发送区填入的是一个非完整的json文本,比如”12345”, 子进程不会做任何清空处理。
4.2 主进程接收区
接收区用于主进程接收数据用的,主进程需要主动去读取该接收区内容,当主进程通过发送区发送一段json文本后,如果json文本有返回内容,那么就要开启定时器去轮询读取子进程返回的数据内容。如果读取到内容,主进程可以通过buff[0]=’\0’进行清空处理
4.3 心跳区
用于检测子进程是否启动成功,并检测子进程是否正常运行
当主进程启动子进程并且参数格式正确后,子进程会立即更新心跳区内容为一个正整数unsigned int数字,然后会定时1S去进行数字加1(从0递增),并更新内容中的数字。
提示:
- 主进程可以启动子进程后,去定时轮询心跳区内容,如果更新为一个数字说明启动子进程成功,如果超过5秒都没内容,说明启动进程失败,即可弹窗通知报错等操作。
- 当检测到启动成功后,可以定时去读取心跳区内容是否每隔2秒进行更新,如果长时间未更新说明子进程挂掉或通信失败、需要杀掉所有同名子进程,然后重新启动一个新的子进程来进行交互处理。
4.4 主进程核心代码
主进程头文件成员如下所示:
QList<QSharedMemory*> _memList; QSharedMemory _memTx; QSharedMemory _memRx; QSharedMemory _memHeartbeat; unsigned int _heart{999};主进程示例cpp代码如下所示:
#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QMessageBox> #include <QTimer> #include <QProcess> #include <QJsonDocument> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) , _memTx(this) , _memRx(this) , _memHeartbeat(this) { ui->setupUi(this); _memTx.setNativeKey("_memTxTest"); _memRx.setNativeKey("_memRxTest"); _memHeartbeat.setNativeKey("_memHeartbeatTest"); qDebug()<<"_memTx:"<<_memTx.nativeKey(); _memList = {&_memTx, &_memRx, &_memHeartbeat}; int size = 4096; for(int i = 0; i < _memList.length();i++) { if(_memList[i]->isAttached()) { // 如果已存在,则先尝试分离,再重新创建,这是一种更健壮的做法 _memList[i]->detach(); } if (!_memList[i]->create(size)) { qDebug() << "创建共享内存失败:" << _memList[i]->errorString(); QTimer::singleShot(2000, this, [&]{ QMessageBox::warning(NULL,"创建共享内存失败", "创建共享内存失败",QMessageBox::Ok); QApplication::instance()->quit(); }); return; } qDebug() << "共享内存创建成功,大小:" << _memList[i]->size(); } QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, [&]{ if (!_memRx.isAttached()) { if (!_memRx.attach()) { qDebug() << "attach _memRx.errorString:" << _memRx.errorString(); } } if(_memRx.isAttached()) { _memRx.lock(); QByteArray arr((char*)_memRx.data()); if(arr.length()) { // 处理主进程接收区域内容,处理完后 dest[0] = '\0' 清空处理 qDebug()<<"_memRx arr"<<arr.length(); ui->_textRecv->setPlainText(QString(arr)); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(arr, &error); if(error.error == QJsonParseError::NoError) { char *dest = reinterpret_cast<char *>(_memRx.data()); dest[0] = '\0'; } qDebug()<<"recv _memRx:"<<arr.length()<<arr; } _memRx.unlock(); } if (!_memHeartbeat.isAttached()) { if (!_memHeartbeat.attach()) { qDebug() << "attach _memHeartbeat.errorString:" << _memHeartbeat.errorString(); } } if(_memHeartbeat.isAttached()) { _memHeartbeat.lock(); QByteArray memHeartbeat((char*)_memHeartbeat.data()); if(memHeartbeat.length()) { // 处理心跳值 uint data = QString(memHeartbeat).toUInt(); if(data != _heart) { ui->_textHeartbeat->setPlainText(QString(memHeartbeat)); qDebug()<<"recv _memHeartbeat:"<<memHeartbeat.length()<<memHeartbeat; _heart = data; } } _memHeartbeat.unlock(); } }); timer->start(200); } Widget::~Widget() { delete ui; } void Widget::on_pushButton_2_clicked() // 打开子进程 按钮 { QProcess* m_curProcess = new QProcess(this); m_curProcess->start(QCoreApplication::applicationDirPath() + "/" + ui->lineExeName->text(), { _memTx.nativeKey() ,_memRx.nativeKey() ,_memHeartbeat.nativeKey() }); } void Widget::on_pushButton_clicked() // 发送 按钮(下发通信) { QByteArray arr = ui->_textSend->toPlainText().toUtf8(); int len = arr.length(); if(!len) return; _memTx.lock(); char *dest = reinterpret_cast<char *>(_memTx.data()); strcpy(dest, arr); _memTx.unlock(); } void Widget::on_pushButton_3_clicked() // 读回发送区域按钮,读取发送区域,判断子进程是否处理完成 { char *dest = reinterpret_cast<char *>(_memTx.data()); qDebug()<<"读回发送区域:"<<dest; ui->_textSend->setPlainText(dest); _memTx.unlock(); }4.5 子进程核心代码
头文件相关成员和函数如下所示:
void iniMemorys(QString masterSend, QString masterRecv, QString heartbeat); void sendMaster(QVariantMap map); void handlerMasterData(QVariantMap map); QSharedMemory _masterTx; QSharedMemory _masterRx; QSharedMemory _memHeartbeat; unsigned int _memHeartbeatCnt; // 心跳值cpp相关函数实现如下所示:
void ShareMemoryComm::iniMemorys(QString masterSend, QString masterRecv, QString heartbeat) { _masterTx.setNativeKey(masterSend); _masterRx.setNativeKey(masterRecv); _memHeartbeat.setNativeKey(heartbeat); _memHeartbeatCnt = 0; QTimer *heartbeatTimer = new QTimer(this); connect(heartbeatTimer, &QTimer::timeout, this, &ShareMemoryComm::heartbeatTimeout); heartbeatTimeout(); // 立即发一次心跳,告诉主进程启动成功并通信正常 heartbeatTimer->setInterval(1000); heartbeatTimer->start(); QTimer *masterTxTimer = new QTimer(this); connect(masterTxTimer, &QTimer::timeout, this, [&]{ if (!_masterTx.isAttached()) { if (!_masterTx.attach()) { qDebug() << "attach _masterTx.errorString:" << _masterTx.errorString(); return; } } _masterTx.lock(); QByteArray arr((char*)_masterTx.data()); if(arr.length()) { qDebug()<<"_masterTx 接收内容:"<<arr.length()<<arr.left(40); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(arr, &error); if(error.error == QJsonParseError::NoError) { handlerMasterData(doc.toVariant().toMap()); char *dest = reinterpret_cast<char *>(_masterTx.data()); dest[0] = '\0'; } } _masterTx.unlock(); }); masterTxTimer->setInterval(200); masterTxTimer->start(); } void ShareMemoryComm::heartbeatTimeout() { if (!_memHeartbeat.isAttached()) { if (!_memHeartbeat.attach()) { qDebug() << "attach _memHeartbeat.errorString:" << _memHeartbeat.errorString(); return; } } _memHeartbeat.lock(); QByteArray arr = QString::number(_memHeartbeatCnt++).toUtf8(); char *dest = reinterpret_cast<char *>(_memHeartbeat.data()); strcpy(dest, arr); qDebug()<<"_memHeartbeatCnt:"<<_memHeartbeatCnt; _memHeartbeat.unlock(); } void ShareMemoryComm::handlerMasterData(QVariantMap map) { if(!map.contains("type")) return; QString type = map["type"].toString(); if(type == "messageBox") { QString title = map["title"].toString(); QString text = map["text"].toString(); map["success"] = true; map["message"] = ""; sendMaster(map); Backend::instance()->messageBoxHintShow(title, text); } } void ShareMemoryComm::sendMaster(QVariantMap map) { QByteArray arr = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact); if (!_masterRx.isAttached()) { if (!_masterRx.attach()) { qDebug() << "attach _masterRx.errorString:" << _masterRx.errorString(); return; } } _masterRx.lock(); if(_masterRx.size() > arr.size()) { char *dest = reinterpret_cast<char *>(_masterRx.data()); strcpy(dest, arr); } _masterRx.unlock(); }