利用线程与环形缓冲区实现对数据的异步存储

利用线程与环形缓冲区实现对数据的异步存储

一、流程图

在这里插入图片描述

  • 数据流首先,进入缓存至环形缓冲区,当缓冲区中一个缓存块被填满后,释放信号量给线程;
  • 线程被唤醒,从缓冲区中取出一个缓存块的数据,并写入文件。

以上,就是2个主要过程,如此循环往复。当然还有很多细节没有介绍,诸如,缓存块读、写指针的控制等。

二、FileSave文件异步存储类

以上所讲过程,全部封装到了FileSave类中。

数据推送接口,RealUCharPort.h

#ifndef REALUCHARPORT_H
#define REALUCHARPORT_H

#pragma once

class RealUCharPort
{
public:
    virtual void pushPacket(unsigned char* pPkt,unsigned int iLen)=0;
};

#endif // REALUCHARPORT_H

FileSave.h

#ifndef FILESAVE_H
#define FILESAVE_H

#include <stdio.h>
#include "RealUCharPort.h"
#include "Mutex.h"

// 最大缓存块个数,以512KB为一个缓存块
#define  BUFBLOCKNUM_MAX    10
// 默认有效缓存块个数,以512KB为一个缓存块
#define  BUFBLOCKNUM_DEF    6
// 一个缓存块的大小
#define BLOCKSIZE           (1 * 512 * 1024)

class FileSave : public RealUCharPort
{
public:
    // 构造函数
    FileSave();
    // 虚构函数
    virtual ~FileSave(void);

    // 开始记录
    bool Start(const char* FileName, unsigned int BlockNum = BUFBLOCKNUM_DEF);
    // 停止记录
    bool Stop();

    // 接收数据函数
    void pushPacket(unsigned char* pPkt,unsigned int iLen);
    // 保存数据线程
    void SaveData();

private:
    // 是否开始记录
    bool m_bStart;

    // 接收缓冲区
    unsigned char* m_RxBuf[BUFBLOCKNUM_MAX];
    unsigned int m_RxLen[BUFBLOCKNUM_MAX];

    // 读、写指针
    unsigned int m_iWr_Block_Ptr;
    unsigned int m_iWr_Ptr;
    unsigned int m_iRd_Block_Ptr;

    // 0-数据到达通知信号量句柄
    // 1-通知线程退出信号量句柄
    HANDLE m_hDataReady[2];

    // 存储线程句柄
    HANDLE m_hSaveThread;

    // 文件
    Mutex m_mFileLock;
    FILE* m_pFile;

    // 实际有效块数
    unsigned int m_iBufBlockSize;
    Mutex m_mBufLock;
    char*    _fileBuf;
};

#endif // FILESAVE_H

FileSave.cpp

#include "FileSave.h"
#include <QDebug>
#include <QMessageBox>

DWORD WINAPI SaveDataThread(LPVOID lpParameter)
{
    FileSave* pSave = (FileSave*)lpParameter;

    pSave->SaveData();

    return(0);
}

FileSave::FileSave()
    : m_pFile(NULL),
      m_bStart(false),
      m_iBufBlockSize(0),
      m_iWr_Block_Ptr(0),
      m_iWr_Ptr(0),
      m_iRd_Block_Ptr(0),
      _fileBuf(NULL)
{
    //缓冲区初始化
    for(int i = 0; i < BUFBLOCKNUM_MAX; i++)
    {
        m_RxBuf[i] = NULL;
        m_RxLen[i] = 0;
    }

    //初始化句柄
    m_hDataReady[0] = NULL;
    m_hDataReady[1] = NULL;
    m_hSaveThread = NULL;

    //创建信号量
    m_hDataReady[0] = CreateSemaphore(NULL, 0, BUFBLOCKNUM_MAX, NULL);
    if(m_hDataReady[0] == NULL)
    {
        return;
    }
    m_hDataReady[1] = CreateSemaphore(NULL, 0, 1, NULL);
    if(m_hDataReady[1] == NULL)
    {
        CloseHandle(m_hDataReady[0]);
        m_hDataReady[0] = NULL;
        return;
    }

    //创建一个线程用于保存文件
    m_hSaveThread = CreateThread(NULL, 0, SaveDataThread, this, 0, NULL);
    if(m_hSaveThread == NULL)
    {
        CloseHandle(m_hDataReady[0]);
        m_hDataReady[0] = NULL;
        CloseHandle(m_hDataReady[1]);
        m_hDataReady[1] = NULL;
        return;
    }
}

FileSave::~FileSave(void)
{
    if(m_hSaveThread != NULL)
    {
        //发出线程退出通知事件
        ReleaseSemaphore(m_hDataReady[1], 1, NULL);
        //等待线程正常退出(最多等待3000ms)
        if(WAIT_TIMEOUT == WaitForSingleObject(m_hSaveThread, 3000))
        {
            TerminateThread(m_hSaveThread, 0); // 如果线程超时未退出,强制终止线程
        }

        //关闭句柄
        CloseHandle(m_hDataReady[0]);
        m_hDataReady[0] = NULL;
        CloseHandle(m_hDataReady[1]);
        m_hDataReady[1] = NULL;
        CloseHandle(m_hSaveThread);
        m_hSaveThread = NULL;
    }

    //关闭文件
    if(m_bStart)
    {
        Stop();
        m_bStart = false;
    }
}

bool FileSave::Start(const char* FileName, unsigned int BlockNum)
{
    //已经开始了,不得再次开始
    if(m_bStart)
        return false;

    if(FileName == NULL)
        return false;

    //打开文件
    m_pFile = fopen(FileName, "wb+");
    if (m_pFile == NULL)
        return false;

    //申请缓冲空间
    try
    {
        _fileBuf = new char[BLOCKSIZE];
        for(unsigned int i = 0; i < BlockNum; i++)
        {
            m_RxBuf[i] = new unsigned char[BLOCKSIZE];
            m_RxLen[i] = 0;
        }
    }
    catch(...)
    {
        for(unsigned int i = 0; i < BlockNum; i++)
        {
            if (m_RxBuf[i] != NULL)
            {
                delete m_RxBuf[i];
                m_RxBuf[i] = NULL;
                m_RxLen[i] = 0;
            }
        }
        if (_fileBuf != NULL)
        delete _fileBuf;
        _fileBuf = NULL;

        fclose(m_pFile);
        m_pFile = NULL;

        QMessageBox::warning(NULL, QObject::tr("warning"), QObject::tr("Memory request failed"));
        return false;
    }

    setvbuf(m_pFile, _fileBuf, _IOFBF, BLOCKSIZE);

    for(int i = BlockNum; i < BUFBLOCKNUM_MAX; i++)
    {
        m_RxBuf[i] = NULL;
        m_RxLen[i] = 0;
    }
    m_iBufBlockSize = BlockNum;

    m_iWr_Block_Ptr = 0;
    m_iWr_Ptr = 0;
    m_iRd_Block_Ptr = m_iBufBlockSize - 1;

    m_bStart = true;
    return true;
}

bool FileSave::Stop()
{
    if(!m_bStart) return false;

    m_bStart = false;

    m_mFileLock.lock();

    fclose(m_pFile);
    m_pFile = NULL;

    delete _fileBuf;
    _fileBuf = NULL;

    m_mFileLock.unlock();

    //释放原有缓存空间
    for(int i = 0; i < BUFBLOCKNUM_MAX; i++)
    {
        if(m_RxBuf[i] != NULL)
        {
            delete []m_RxBuf[i];
        }
        m_RxBuf[i] = NULL;
        m_RxLen[i] = 0;
    }
    m_iBufBlockSize = 0;

    return true;
}

void FileSave::pushPacket(unsigned char* pPkt,unsigned int iLen)
{
    if(!m_bStart) return;

    m_mBufLock.lock();

    if((m_iWr_Ptr+iLen) > BLOCKSIZE)
    {
        m_RxLen[m_iWr_Block_Ptr] = m_iWr_Ptr;
        ReleaseSemaphore(m_hDataReady[0], 1, NULL);
        m_iWr_Ptr = 0;
        m_iWr_Block_Ptr = (m_iWr_Block_Ptr + 1) % m_iBufBlockSize;

        //m_mFileLock.Lock();
        if(m_iWr_Block_Ptr == m_iRd_Block_Ptr)
        {
            //TRACE("缓冲区溢出:Wr=%d,Rd=%d\n",m_iWr_Block_Ptr,m_iRd_Block_Ptr);
            //qDebug("缓冲区溢出:Wr=%d,Rd=%d\n",m_iWr_Block_Ptr,m_iRd_Block_Ptr);
        }
        //m_mFileLock.Unlock();
    }

    memcpy(m_RxBuf[m_iWr_Block_Ptr] + m_iWr_Ptr, pPkt, iLen);
    m_iWr_Ptr += iLen;

    m_mBufLock.unlock();
}

void FileSave::SaveData()
{
    while(true)
    {
        //等待事件
        DWORD dwEvent = WaitForMultipleObjects(2, m_hDataReady, FALSE, 1000);
        if (dwEvent == WAIT_OBJECT_0 + 0)  // 数据到达
        {
            //保存数据到文件
            if(m_bStart)
            {
                // 获取读地址与数据长度
                m_mBufLock.lock();

                m_iRd_Block_Ptr = (m_iRd_Block_Ptr + 1) % m_iBufBlockSize;

                //存盘
                //m_mFileLock.lock();
                fwrite(m_RxBuf[m_iRd_Block_Ptr], m_RxLen[m_iRd_Block_Ptr], 1, m_pFile);
                //fflush(m_pFile);
                //m_mFileLock.unlock();

                m_mBufLock.unlock();
            }
        }
        else if(dwEvent == WAIT_OBJECT_0 + 1)  // 线程退出
        {
            break;
        }
        else if (dwEvent == WAIT_TIMEOUT)  // 超时
        {
            if(m_bStart)
            {
                m_mBufLock.lock();

                if(m_iWr_Ptr != 0)
                {
                    m_RxLen[m_iWr_Block_Ptr] = m_iWr_Ptr;
                    ReleaseSemaphore(m_hDataReady[0], 1, NULL);
                    m_iWr_Ptr = 0;
                    m_iWr_Block_Ptr = (m_iWr_Block_Ptr + 1) % m_iBufBlockSize;
                }

                m_mBufLock.unlock();
            }
        }
    }
}

代码实现看起来,较麻烦。但是,使用起来,倒是十分简单,主要就是如下3个函数:

// 开始记录
bool Start(const char* FileName, unsigned int BlockNum = BUFBLOCKNUM_DEF);
// 停止记录
bool Stop();

// 接收数据函数
void pushPacket(unsigned char* pPkt,unsigned int iLen);

具体用法,看如下的测试代码,mainwindow.cpp。

三、测试代码

mainwindow.cpp

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->pushButton_start->setEnabled(true);
    ui->pushButton_stop->setEnabled(false);

    // 生成256KB测试数据,因为每次推送数据不能大于BLOCKSIZE=512KB
    createTestData(data, 256 * 1024);
    fileSave = new FileSave();

    // 每隔1s推送256KB数据,用于模拟真实接收数据环境
    startTimer(1000);
}

MainWindow::~MainWindow()
{
    delete fileSave;
    fileSave = nullptr;

    delete ui;
}

void MainWindow::timerEvent(QTimerEvent *event)
{
    fileSave->pushPacket((unsigned char*)data.data(), data.length());
}

void MainWindow::createTestData(QByteArray& data, int size)
{
    data.resize(size);
    for (int i = 0; i < size; i++)
    {
        data[i] = i % 128;
    }
}

void MainWindow::on_pushButton_start_clicked()
{
    QString fileName = QUuid::createUuid().toString();
    QString filePath = qApp->applicationDirPath() + "/" + fileName + ".data";
    fileSave->Start(filePath.toStdString().c_str());

    ui->pushButton_start->setEnabled(false);
    ui->pushButton_stop->setEnabled(true);
}

void MainWindow::on_pushButton_stop_clicked()
{
    fileSave->Stop();

    ui->pushButton_start->setEnabled(true);
    ui->pushButton_stop->setEnabled(false);
}

先生成256KB测试数据,并通过定时器每隔1s,将该数据进行一次推送,用于模拟真实接收到数据的情景。

在定时器响应函数中,通过FileSave的pushPacket接口,将数据推送到内部的环形缓冲区。

  • 当点击“开始存储”按钮时,创建文件,并在独立的线程中,自动从环形缓冲区中,不断取出数据,并写入文件。

  • 当点击“停止存储”按钮时,停止取数据,并关闭文件。

当没有开始存储时,线程不会从缓冲区中取数据,缓冲区中的数据,会自动被循环覆盖(新的数据块自动覆盖最久远的数据块)。

该类存储的行为,类似于从流动的河水中,从某个位置,持续取出一段水流。

运行效果:

在这里插入图片描述

在这里插入图片描述

本文涉及工程代码:

https://gitee.com/bailiyang/cdemo/tree/master/C++/23FileSaveTest/FileSaveTest



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

百里杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值