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 modeReadWrite) //创建一个大小为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_castchar *(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 -m2、删除共享内存使用命令ipcrm -m [shmid]如下图所示:1、key共享内存的key2、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 主进程核心代码主进程头文件成员如下所示:QListQSharedMemory* _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 arrarr.length(); ui-_textRecv-setPlainText(QString(arr)); QJsonParseError error; QJsonDocument doc QJsonDocument::fromJson(arr, error); if(error.error QJsonParseError::NoError) { char *dest reinterpret_castchar *(_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_castchar *(_memTx.data()); strcpy(dest, arr); _memTx.unlock(); } void Widget::on_pushButton_3_clicked() // 读回发送区域按钮读取发送区域判断子进程是否处理完成 { char *dest reinterpret_castchar *(_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_castchar *(_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_castchar *(_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_castchar *(_masterRx.data()); strcpy(dest, arr); } _masterRx.unlock(); }