Summary:
项目中我们有时需要使用共享内存共享数据,这样,数据不用进程IO读写,加进数据加载和落地;
程序退出时,再保存到本地;速度提升数十倍;
Part1:QSharedMemory
Windows平台下进程间通信常用方式有管道、文件映射、Socket通信和共享内存等,这里详细介绍一下Qt的共享内存机制。
Qt 框架提供了多种 IPC 技术,其中 QSharedMemory 是一种高效的共享内存方式,可以实现多个进程之间快速交换数据。本文将详细讲解 QSharedMemory 的概念、用法及其主要函数的用途;
Header: | #include <QSharedMemory> |
qmake: | QT += core |
Since: | Qt 4.4 |
Inherits: | QObject |
1.QSharedMemory 的核心特点
唯一键(Key)标识:
每块共享内存通过唯一的键(字符串)标识。
不同进程通过相同的键连接到共享内存。
线程安全性:
提供锁机制(lock() 和 unlock())以保护共享内存的读写。
跨平台支持:
Qt 的跨平台特性使 QSharedMemory 可以在不同操作系统上无缝使用。
2. QSharedMemory 的工作流程
共享内存的基本使用可以分为以下几个步骤:
创建共享内存:
第一个进程通过 create(size) 创建一块共享内存。
分配的大小由数据的存储需求决定。
附加到共享内存:
其他进程通过 attach() 方法连接到已有的共享内存。
数据读写:
通过 lock() 和 unlock() 保证线程安全,获取内存指针后读写数据。
释放共享内存:
调用 detach() 断开与共享内存的连接。
3.QSharedMemory 常用函数
1. 构造函数与析构函数
QSharedMemory(const QString &key, QObject *parent = nullptr)创建一个 QSharedMemory 对象,并指定共享内存的键(key)。key 是共享内存的唯一标识符,多个进程通过相同的 key 访问同一块共享内存。~QSharedMemory()析构函数,释放共享内存资源。2. 共享内存的创建与销毁
bool create(int size, QSharedMemory::AccessMode mode = ReadWrite)创建大小为 size 的共享内存段。mode 指定共享内存的访问模式:QSharedMemory::ReadOnly:只读模式。QSharedMemory::ReadWrite:读写模式(默认)。如果共享内存已存在,则返回 false。bool attach(QSharedMemory::AccessMode mode = ReadWrite)附加到已存在的共享内存段。mode 指定访问模式(同上)。如果附加成功,返回 true。bool detach()从共享内存段分离。分离后,进程不再访问共享内存,但共享内存段仍然存在。void setKey(const QString &key)设置共享内存的键(key)。QString key() const返回共享内存的键。void setNativeKey(const QString &key)设置平台原生的共享内存键(适用于需要直接使用系统共享内存键的场景)。QString nativeKey() const返回平台原生的共享内存键。3. 共享内存的访问
void *data()返回指向共享内存数据的指针。如果共享内存未附加或未创建,返回 nullptr。const void *constData() const返回指向共享内存数据的常量指针。适用于只读访问。int size() const返回共享内存段的大小(以字节为单位)。4. 共享内存的锁定与解锁
bool lock()锁定共享内存,确保当前进程独占访问。如果锁定成功,返回 true。bool unlock()解锁共享内存,允许其他进程访问。如果解锁成功,返回 true。5. 错误处理
QSharedMemory::SharedMemoryError error() const返回最后一次发生的错误类型。错误类型包括:QSharedMemory::NoError:无错误。QSharedMemory::PermissionDenied:权限不足。QSharedMemory::InvalidSize:无效的大小。QSharedMemory::KeyError:键错误。QSharedMemory::AlreadyExists:共享内存已存在。QSharedMemory::NotFound:共享内存未找到。QSharedMemory::LockError:锁定失败。QSharedMemory::OutOfResources:系统资源不足。QSharedMemory::UnknownError:未知错误。QString errorString() const返回最后一次错误的描述信息。6. 共享内存的状态
bool isAttached() const检查当前进程是否已附加到共享内存段。如果已附加,返回 true。
Part3: 读写文件样例:
写样例:
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
QDataStream out(&buffer);
out.setByteOrder(QDataStream::LittleEndian);// 假设 metaInfo 是已填充的结构体
out.writeRawData((char*)&metaInfo, sizeof(FileMetaInfo));// 将 buffer 数据写入共享内存
sharedMemory.lock();
memcpy(sharedMemory.data(), buffer.data().constData(), buffer.size());
sharedMemory.unlock();
读样例:
QBuffer buffer;qDebug() << "sharedMemory.size():" << sharedMemory.size();buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());buffer.open(QIODevice::ReadOnly);buffer.seek(0);qDebug() << "Buffer size:" << buffer.size();qDebug() << "First bytes:" << QByteArray::fromRawData((char*)sharedMemory.constData(), 16).toHex();QDataStream inStream(&buffer);inStream.device()->seek(0); // 确保 QDataStream 位置正确inStream.setByteOrder(QDataStream::LittleEndian);FileMetaInfo metaPtr ;inStream.readRawData((char*)&metaPtr, sizeof(FileMetaInfo));qDebug() << "File:" << metaPtr.fileName<< "Size:" << metaPtr.fileSize<< "Offset:" << metaPtr.dataOffset;
读写多个文件样例:
共享内存结构定义
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QVector>// 定义文件元信息结构
#pragma pack(1)
struct FileMetaInfo {char fileName[256]; // 文件名qint64 fileSize; // 文件大小qint64 dataOffset; // 文件数据在共享内存中的偏移量FileMetaInfo() : fileSize(0), dataOffset(0) {memset(fileName, 0, sizeof(fileName)); // 初始化 fileName 为空字符串}
};
#pragma pack()
// 定义共享内存的索引区大小(假设最多存储 10 个文件)
const int MAX_FILES = 10;
const int INDEX_SIZE = MAX_FILES * sizeof(FileMetaInfo);
进程A:写入文件到共享内存
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建共享内存对象QSharedMemory sharedMemory("MySharedMemory");// 假设要写入的文件列表QStringList filePaths = { "file1.txt", "file2.txt", "file3.txt" };// 计算共享内存总大小(索引区 + 数据区)qint64 totalDataSize = 0;for (const QString &filePath : filePaths) {QFile file(filePath);if (file.open(QIODevice::ReadOnly)) {totalDataSize += file.size();file.close();}}qint64 totalSharedMemorySize = INDEX_SIZE + totalDataSize;// 创建共享内存if (!sharedMemory.create(totalSharedMemorySize)) {qDebug() << "Failed to create shared memory:" << sharedMemory.errorString();return 1;}// 写入索引区和数据区sharedMemory.lock();// 写入索引区QBuffer indexBuffer;indexBuffer.open(QBuffer::ReadWrite);QDataStream indexStream(&indexBuffer);qint64 currentDataOffset = INDEX_SIZE; // 数据区从索引区之后开始for (const QString &filePath : filePaths) {QFile file(filePath);if (file.open(QIODevice::ReadOnly)) {FileMetaInfo metaInfo;metaInfo.fileName = QFileInfo(file).fileName();metaInfo.fileSize = file.size();metaInfo.dataOffset = currentDataOffset;// 写入索引信息indexStream << metaInfo.fileName << metaInfo.fileSize << metaInfo.dataOffset;// 写入文件数据到共享内存char *to = (char*)sharedMemory.data() + currentDataOffset;file.read(to, metaInfo.fileSize);currentDataOffset += metaInfo.fileSize;file.close();}}// 将索引区数据写入共享内存char *indexTo = (char*)sharedMemory.data();const char *indexFrom = indexBuffer.data().data();memcpy(indexTo, indexFrom, indexBuffer.size());sharedMemory.unlock();qDebug() << "Files written to shared memory.";return a.exec();
}
进程B:从共享内存读取文件
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建共享内存对象QSharedMemory sharedMemory("MySharedMemory");// 附加到共享内存if (!sharedMemory.attach()) {qDebug() << "Failed to attach to shared memory:" << sharedMemory.errorString();return 1;}// 读取索引区sharedMemory.lock();QBuffer indexBuffer;indexBuffer.setData((char*)sharedMemory.constData(), INDEX_SIZE);indexBuffer.open(QBuffer::ReadOnly);QDataStream indexStream(&indexBuffer);QVector<FileMetaInfo> fileMetaInfos;while (!indexStream.atEnd()) {FileMetaInfo metaInfo;indexStream >> metaInfo.fileName >> metaInfo.fileSize >> metaInfo.dataOffset;fileMetaInfos.append(metaInfo);}// 读取文件数据for (const FileMetaInfo &metaInfo : fileMetaInfos) {QByteArray fileData(metaInfo.fileSize, 0);const char *from = (char*)sharedMemory.constData() + metaInfo.dataOffset;memcpy(fileData.data(), from, metaInfo.fileSize);// 将文件数据保存到本地QFile file(metaInfo.fileName);if (file.open(QIODevice::WriteOnly)) {file.write(fileData);file.close();qDebug() << "File saved:" << metaInfo.fileName;}}sharedMemory.unlock();// 分离共享内存sharedMemory.detach();return a.exec();
}
代码说明
-
共享内存结构:
-
共享内存分为索引区和数据区。
-
索引区存储文件的元信息(文件名、大小、偏移量)。
-
数据区存储实际的文件数据。
-
-
进程A:
-
计算共享内存的总大小(索引区 + 数据区)。
-
将文件的元信息写入索引区。
-
将文件数据写入数据区。
-
-
进程B:
-
从索引区读取文件的元信息。
-
根据元信息从数据区读取文件数据,并保存到本地。
-
-
共享内存的锁定与解锁:
-
在读写共享内存时,使用
lock()
和unlock()
确保数据一致性。
-
-
文件管理:
-
支持多个文件的读写,通过索引区管理文件的元信息。
-
运行步骤
-
编译并运行进程A,将文件写入共享内存。
-
编译并运行进程B,从共享内存读取文件并保存到本地。
Part4: 注意事项:
-
需要确保文件名和文件路径的唯一性,避免冲突。
-
在实际应用中,可能需要处理更多的错误情况(如文件不存在、共享内存不足等)。
-
共享内存的键:多个进程必须使用相同的键才能访问同一块共享内存。
-
锁定与解锁:在读写共享内存时,必须使用
lock()
和unlock()
确保数据一致性。 -
错误处理:始终检查
create()
和attach()
的返回值,并处理可能的错误。 -
共享内存的大小:确保共享内存的大小足够容纳要存储的数据。
-
通过这种方式,可以在共享内存中高效地管理多个文件,并通过索引文件实现快速访问
可能遇到的问题:
1.读写,请使用相关的方式:
indexStream.setByteOrder(QDataStream::LittleEndian);
2.