Qt利用QFile内存映射实现进程间通信

  • 最近工作上有用到进程通信相关的內容,今天来总结一下,看了网上的大部分内容介绍大体上是通过共享内存来实现,主要是通过QSharedMemory类来访问共享内存,实现共享内存的操作,但是这有个不好的地方就是,当两个进程都detach共享内存时,这个内存就会被回收掉,没法实现类似延时读取的功能。在此,我们可以利用QFile的map函数来进程内存映射文件,其本质上也是模拟共享内存。然后进程间同步呢主要是通过QSystemSemaphore类这个系统级信号量来实现。
  • 涉及到的相关接口
uchar * QFileDevice::map(qint64 offset, qint64 size, MemoryMapFlags flags = NoOptions)
offset是映射起始的偏移量,size是映射的内存大小,flags暂时不用
bool QFileDevice::unmap(uchar * address)
回收映射的内存,这里有一个需要注意的是当这个内存映射的文件对象被销毁或者重新打开一个新的文件时,相关的没有回收映射的内存会被自动回收
  • 下面代码示例一对父子进程的通信
  • 父进程主要代码
QSystemSemaphore g_sem("SemRead", 1, QSystemSemaphore::Create);

QtParentTask::QtParentTask(QWidget *parent)
	: QWidget(parent),ui(new Ui::QtParentTaskClass)
	,m_timer(new QTimer(this))
	,m_fp(nullptr)
{
	ui->setupUi(this);
	connect(ui->pushButton, &QPushButton::clicked, this, &QtParentTask::startChildProcess);
	connect(m_timer, &QTimer::timeout, this, &QtParentTask::readChildData);

	QDir dir;
	QString dirpath = QDir::tempPath() + "\\QtIPC_test";
	if (!dir.exists(dirpath))
		dir.mkdir(dirpath);
	ui->progressBar->setMaximum(100000);
	//在系统临时文件夹进行临时文件映射
	m_memFile.setFileName(dirpath + "\\data1");
}

QtParentTask::~QtParentTask()
{
	delete ui; 
}

void QtParentTask::startChildProcess()
{
	m_fp = nullptr;
	const QString path("QtChildTask.exe");
	QProcess childProcess;
	connect(&childProcess, static_cast<void (QProcess::*)(QProcess::ProcessError error)>(&QProcess::error), 
		[this](QProcess::ProcessError error) {
		if (error == QProcess::Crashed)
			ui->lineEdit->setText("crashed");
	});

	//读取子进程的标准输出
	connect(&childProcess, &QProcess::readyReadStandardOutput, [this, &childProcess]() {
		QString txt = QString::fromLocal8Bit(childProcess.readAllStandardOutput());
		ui->lineEdit->setText(txt);
	});
	//读取子进程的标准错误输出
	connect(&childProcess, &QProcess::readyReadStandardError, [this, &childProcess]() {
		QString txt = QString::fromLocal8Bit(childProcess.readAllStandardError());
		ui->lineEdit_err->setText(txt);
	});

	QEventLoop loop;
	connect(&childProcess, static_cast<void (QProcess::*)(int exitCode)>(&QProcess::finished), &loop, &QEventLoop::quit);
	connect(&childProcess, static_cast<void (QProcess::*)(int exitCode)>(&QProcess::finished), [this](int exitCode) {
		if(0 == exitCode)
			ui->lineEdit->setText("childProcess done");
		else
			ui->lineEdit->setText("childProcess exit error");
		//保证读取到最后写入的值
		readChildData();
		if (m_fp) m_memFile.unmap(m_fp);
	});

	childProcess.start(path);
	m_timer->start(50);
	//启动内部循环阻塞到子进程执行完毕
	loop.exec();
	m_timer->stop();
}

void QtParentTask::readChildData()
{
	if(g_sem.acquire())
	{//利用信号量进行进程同步
		if(m_fp == nullptr)
		{
			if (m_memFile.open(QIODevice::ReadOnly))
			{//这里主要是读,不涉及写,子进程负责写
				m_fp = m_memFile.map(0, m_memFile.size());
				//关闭文件不会影响映射的内存
				m_memFile.close();
			}
		}
		
		if(m_fp)
		{
			int val = *((int*)m_fp);
			ui->progressBar->setValue(val);
		}
		g_sem.release();
	}
	else
		QMessageBox::information(this, "error", g_sem.errorString());
}

  • 子进程主要代码
QSystemSemaphore g_sem("SemRead", 1, QSystemSemaphore::Open);
QString g_memFile = QDir::tempPath() + "\\QtIPC_test\\data1";
uchar* fp = nullptr;

void writeSharedData(int val)
{
	if(g_sem.acquire())
	{
		
		if (fp)
		{
			memcpy(fp, &val, sizeof(int));
			
		}
		else
			std::cerr << file.errorString().toLocal8Bit().constData();
		

		g_sem.release();
	}
	else
		std::cerr << g_sem.errorString().toLocal8Bit().constData();
}

int main(int argc, char *argv[])
{
	QFile file(g_memFile);
	//得先设置大小否则映射会失败
	bool isok = file.resize(1024);
	bool isok1 = file.open(QIODevice::ReadWrite);
	fp = file.map(0, 1024);
	file.close();
	for (int i = 0; i <= 100000; i++)
	{
		std::cout << i << std::endl;
		writeSharedData(i);
	}
	file.unmap(fp);
	return 0;
}

示例代码下载:https://download.csdn.net/download/github_39553168/12374335/.

你可能感兴趣的:(Qt学习指南,qt5,多进程)