工程实战:MFC新开线程+缓存队列存文件类的实现

前言

想从网口持续接收大量数据并存文件,做上位机界面。因为还有其他功能要占用界面资源,需要新开存文件线程。
至于缓存队列,当接收数据线程收到数据后,不希望因为存文件操作占用资源影响接收数据,将接收到的数据先缓存到队列中,存文件线程再从队列中取数据,一般来说存磁盘的速度是比网口速度快的,所以只要缓存队列稍微大些便不会溢出。当然类中也设计了等待机制,如果存文件缓存队列满了,会先存文件,等待队列空闲时再接收数据。
做一个存文件类,流出来选择路径和Write接口,新建对象、选择路径,直接调用Write(BYTE* data,int len)成员便可将数据保存到指定路径下。
保存过程:新建一个dat文件,当文件大小超过m_MaxFileSize(单位为Byte)后,再新建一个dat文件继续存文件。

使用说明

头文件以及原文件已经上传到我的GitHub
首先在想存文件的地方新建一个对象。

FileSave file_test;

只需控制四个接口便可以完成数据存储。其中文件最大容量默认为1GB,可以不设置。

	int SetFilePath(CString str);	// 根据传入的字符串设置文件路径 
	int SelectFilePath();			// 从windows资源管理器选择文件路径
	int Write(BYTE* data,int len);	// 写数据到文件
	int SetMaxFileSize(int size);	// 指定文件最大容量

Demo:

新建一个MFC程序,添加两个按钮,
在这里插入图片描述
编辑代码

#include "FileSave.h"
/*省略MFC自动生成的代码*/
FileSave file_test;
// 选择路径
void CfiletestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	file_test.SelectFilePath();
}
// 存数据
void CfiletestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	int len = 1024;
	BYTE* data = new BYTE[1024];
	for (int i = 0; i < len; i++)
	{
		data[i] = i;
	}
	for (int i = 0; i < len; i++)
		file_test.Write(data, len);
	delete data;
}

运行程序,选择路径后,点击造数存文件,每次点击会写入1MB数据,存得数据如下:
在这里插入图片描述

关键代码

缓存机制

整体思路是,在类中定义一个私有自定义结构体数组,每个结构体是队列的基本单元,结构体只存储每次要存储数据的长度(单位为Byte)以及数据首指针。入队列时动态申请内存,出队列时会释放动态申请的内存,在析构函数中也会检查有没有未释放的内存并进行释放。

自定义缓存队列,首先自定义结构体。

typedef struct _DataPak
{
	int dataLen;	// 数据长度,单位为Byte
	BYTE* data;		// 数据内容

}DataPak;

在类中定义私有队列数组,出入队列指针,以及出入队列锁。

	DataPak* m_SaveDataTeamIn;
	DataPak* m_SaveDataTeamOut;
	DataPak m_SaveDataTeam[MAX_FILE_TEAM];
	CCriticalSection m_InsSaveDataCriSec;
	CCriticalSection m_PopSaveDataCriSec;

队列初始化。令入指针和出指针都指向数组头。数据长度初始化为0,同时代表着数据指针并未申请动态内存。注意数据指针也需要赋初值,否则会出现野指针。

// 数据存文件队列初始化
void FileSave::InitSaveDataTeam()
{
	m_SaveDataTeamIn = m_SaveDataTeamOut = m_SaveDataTeam;
	for (int i = 0; i < MAX_FILE_TEAM; i++)
	{
		m_SaveDataTeam[i].dataLen = 0;
		m_SaveDataTeam[i].data = NULL;
	}
}

入队列。其中SaveDataTeamFull是自定义函数,判断队列是否为空。
这里有一个关键的概念:临界区加锁。这是因为收到数据时数据入队列,而存文件线程会一直从队列里取数进行存文件,如果同时对同一地址读写便会出现问题。通过临界区加锁使得对同一地址空间不被同时访问。
另外,有可能数据接收速度大于存文件速度,导致存文件缓存队列满。通过bWait控制对剩余包的处理策略,包括直接丢弃、等待两种选择。

// 插入数据存文件队列
// 队列满时 bWait为True 等待,bWait为false 不等待返回错误
int FileSave::InsertSaveDataToTeam(BYTE* data,int len, BOOL bWait)
{
	m_InsSaveDataCriSec.Lock();										// 使用临界区加锁

	if(bWait)// 队列满时等待
	{
		m_PopSaveDataCriSec.Lock();

		while(SaveDataTeamFull() && m_FileSaveThreadRun)				// 当T线程需要停止时,此处应放弃继续写数据;
		{
			if(m_SaveDataTeamOut == (m_SaveDataTeam + MAX_FILE_TEAM - 1))
				m_SaveDataTeamOut = m_SaveDataTeam;
			else
				m_SaveDataTeamOut++;
		}
		Sleep(10);
		m_PopSaveDataCriSec.Unlock();
	}
	else// 队列满时返回错误
	{
		if(SaveDataTeamFull())
		{
			m_InsSaveDataCriSec.Unlock();								// 解锁
			return -1;
		}
	}

	// 入队列
	m_SaveDataTeamIn->dataLen = len;
	m_SaveDataTeamIn->data = new BYTE[len];
	memcpy(m_SaveDataTeamIn->data, data, len);
	
	if(m_SaveDataTeamIn == (m_SaveDataTeam + MAX_FILE_TEAM - 1))
		m_SaveDataTeamIn = m_SaveDataTeam;
	else
		m_SaveDataTeamIn++;

	m_InsSaveDataCriSec.Unlock();										// 解锁

	return 0;
}

关于队列的其他函数这里不再一一介绍,有不明白或者发现我代码有漏洞的,欢迎在评论区指出。

线程

本来是想着将线程也变成类的成员,这样可以少给出很多接口,将这个类封装的更漂亮,然而翻阅很多资料之后并不知如何操作,这里暂时使用类外线程。
设计到线程的函数包括线程的初始化、开始、结束。

	void InitDataSaveToFileThread();	// 初始化存文件线程
	void StartDataSaveToFileThread();	// 启动存文件线程
	void StopDataSaveToFileThread();	// 关闭存文件线程

其中线程启动函数如下,AfxBeginThread的第二个参数可以将类创建的对象的指针传入外部线程,进行后续的操作。

void FileSave::StartDataSaveToFileThread()
{
	m_FileSaveThreadRun = TRUE;

	// 第二个参数为传入线程的参数,类型为LPVOID
	AfxBeginThread(FileSaveThread, (LPVOID)this, THREAD_PRIORITY_NORMAL, 0, 0, NULL);
}

以下便是存文件线程。将传进来的参数强制转换为自定义类FileSave的对象指针,进一步访问对象的公有成员。

// 存文件线程
UINT FileSaveThread(LPVOID pParam)
{
	FileSave *filesave = (FileSave*)pParam;

	filesave->SetThreadExit(false);	                                        // 线程退出标志置FALSE
	// g_WriteFileLength = 0;
	while(filesave->GetThreadRunStatus())  
	{
		DataPak pDataPak;

		int SaveData = filesave->GetDataFromTeam(&pDataPak);	// 有数据时返回1

		if(SaveData>0)					
		{
			filesave->TreadSave(&pDataPak);
		}
		else
		{
			// 队列为空时等待
			Sleep(10);
		}
	}
	filesave->SetThreadExit(true);	 		// 线程退出标志置TRUE

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fourier_1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值