QT中级(6)基于QT的文件传输工具(2)

QT中级(6)基于QT的文件传输工具(2)

  • 本文实现第一步
  • 1 新增功能
  • 2 运行效果
  • 3 实现思路
  • 4 源代码

实现这个文件传输工具大概需要那几步?

  1. 实现多线程对文件的读写
  2. 实现TCP客户端和服务端
  3. 实现网络传输

书接上回:QT中级(5)多线程读取一个文件,并在另一个文件夹中合成这个文件(1)

本文实现第一步

1 新增功能

  1. 增加线程暂停和取消功能
  2. 增加意外关闭软件,再次打开可以重新续传
  3. 增加对线程的析构

2 运行效果

QT中级(6)基于QT的文件传输工具(2)_第1张图片

3 实现思路

  1. 通过创建标识符,控制线程的开始、暂停、取消(结束)
//线程工作状态
enum class TransferState {
    None,
    Running,
    Paused,
    Canceled,
    Unexpected
};
  1. 增加配置文件,记住软件运行的状态和一些文件相关信息
static QString configFilePath; //配置文件路径
static QString sourceFilePath; //源文件路径
static QString targetFilePath; //目标文件路径

static QString lastUnexpectedFileName;       //上次没有传输完成的源文件名字
static QString lastUnexpectedTargetFileName; //上次没有传输完成的目标文件名字
static quint64 unexpectedFileSize;           //上次文件已传输大小
static quint64 unexpectedFilePos;            //上次文件传输的位置
  1. 在主线程中的析构函数中增加对工作线程的析构,需要注意的是可以通过向工作线程中传入相应的状态,控制线程是否结束,如果强制关闭软件,而线程并不结束
MainWindow::~MainWindow()
{
    readThread->setStatus(AppConfig::TransferState::Unexpected);
    writeThread->setStatus(AppConfig::TransferState::Unexpected);
    threadPool->waitForDone();
    delete readThread;
    delete writeThread;
    delete ui;
}

4 源代码

源代码下载链接

work.h

#ifndef WORK_H
#define WORK_H

#include 
#include 
#include "appconfig.h"
#define DATA_SIZE 1024 * 1024

class ReadWork : public QObject,public QRunnable
{
    Q_OBJECT
public:
    explicit ReadWork(QObject *parent = nullptr);
    ~ReadWork();
    void run() override;

    void getFile(const QString &filePath);
    void setStatus(const AppConfig::TransferState &status);

signals:
    void sendData(const QByteArray &data);
    void sendFileInfo(const QString &fileInfo);
    void msgTips(const QString &msg);
    void updatePgBar(int num);
private:
    QString filePath;
    AppConfig::TransferState status;
};


class WriteWork : public QObject,public QRunnable
{
    Q_OBJECT
public:
    explicit WriteWork(QObject *parent = nullptr);
    ~WriteWork();
    void run() override;

    void getPath(const QString &filePath);
    void setStatus(const AppConfig::TransferState &status);

public slots:
    void getData(const QByteArray &data);
    void getFileInfo(const QString &data);
signals:
    void msgTips(const QString &msg);
    void updatePgBar(int num);
private:
    QString filePath;
    QString fileInfo;
    QList<QByteArray> arryList;
    AppConfig::TransferState status;
};

#endif // WORK_H

work.cpp

void ReadWork::run()
{
    //判断文件是否存在
    QFileInfo fileInfo(filePath);
    if(!fileInfo.exists())
    {
        emit msgTips("文件不存在!");
        return;
    }

    quint64 sourceFileSize = fileInfo.size();
    quint64 fileSize = (AppConfig::lastUnexpectedFileName.isEmpty())? 0 : AppConfig::unexpectedFileSize;//已经传输的字节
    quint64 filePos = AppConfig::unexpectedFilePos;
    //获得文件信息
    QString fileInfoStr = QString("%1|%2|%3|%4|%5")
            .arg(fileInfo.fileName()).arg(sourceFileSize).arg(fileInfo.absoluteFilePath())
            .arg(fileInfo.suffix()).arg(fileSize);

    emit sendFileInfo(fileInfoStr);
    emit msgTips(fileInfoStr);

    //打开文件
    QFile file(filePath);
    if(!file.open(QIODevice::ReadOnly))
    {
        emit msgTips(QString("文件打开失败!%1。").arg(file.errorString()));
        return;
    }


    QByteArray data;//存储发送的数据
    //移动文件指针
    file.seek(filePos);
    //读取文件
    while(!file.atEnd())
    {
        //取消
        if(status != AppConfig::TransferState::Canceled)
        {
            //意外关闭
            if(status == AppConfig::TransferState::Unexpected)
            {
                //记住文件名称
                AppConfig::lastUnexpectedFileName = filePath;
                //记住已经传输的字节
                AppConfig::unexpectedFileSize = fileSize;
                //记住文件指针的位置,方便下次传输
                AppConfig::unexpectedFilePos = file.pos();
                AppConfig::writeConfig();
                file.close();
                return;
            }

            //暂停
            if(status != AppConfig::TransferState::Paused)
            {
                int readSize = (sourceFileSize-file.pos()<DATA_SIZE)?(sourceFileSize-file.pos()):DATA_SIZE;
                data = file.read(readSize);
                fileSize += data.size();
                int pgBarStep = static_cast<int>(fileSize*100/sourceFileSize);
                emit updatePgBar(pgBarStep);
                emit sendData(data);
                QThread::msleep(100);
            }
            else
            {
                QThread::msleep(100);
            }//if(status != AppConfig::TransferState::Paused)
        }
        else
        {
            file.close();
            emit updatePgBar(0);
            emit msgTips("取消文件传输!");
            return;
        }


    }
    file.close();
    emit msgTips("读取完毕!");
}

写线程
void WriteWork::run()
{
    //如果文件路径为空,则使用当前应用程序所在路径
    if(filePath.isEmpty())
        filePath = QCoreApplication::applicationDirPath();
    //创建文件
    QFile file;
    quint64 sourceFileSize = 0;
    quint64 currentFileSize = 0;
    QString fileName="";

    //获取并解析文件信息
    if(fileInfo.isEmpty())
    {
        emit msgTips("未收到文件信息");
        return;
    }

    QStringList fileInfos = fileInfo.split("|");
    currentFileSize = fileInfos.last().toUInt();
    fileName = fileInfos.at(0);
    sourceFileSize = fileInfos.at(1).toInt();

    //判断文件是不是重传文件
    if(currentFileSize > 0)
    {
        fileName = AppConfig::lastUnexpectedTargetFileName;
        file.setFileName(fileName);
        file.rename(fileName.chopped(4));
    }
    else
    {
        //设置文件相关信息
        file.setFileName(filePath+"/"+fileName);
    }


    emit msgTips("设置文件路径和名称完毕!");

    //打开文件
    if(!file.open(QIODevice::ReadWrite))
    {
        emit msgTips("文件打开失败:"+file.errorString());
        return;
    }
    emit msgTips("文件打开成功!");

    //写入数据
    file.seek(currentFileSize);
    while(sourceFileSize-currentFileSize>0)
    {
        //意外关闭-等待旧数据处理完毕才能执行
        if(status == AppConfig::TransferState::Unexpected && arryList.isEmpty())
        {
            file.close();
            //更改文件名称-在文件后缀中增加.tmp
            if(file.fileName().count(".tmp")>0)
                return;
            file.rename(file.fileName()+".tmp");
            AppConfig::lastUnexpectedTargetFileName = file.fileName();
            AppConfig::writeConfig();
            return;
        }

        //取消
        if(status == AppConfig::TransferState::Canceled)
        {
            file.close();
            file.remove();
            arryList.clear();
            emit msgTips("文件取消传输");
            emit updatePgBar(0);

            if(!AppConfig::lastUnexpectedTargetFileName.isEmpty())
            {
                QFile::remove(AppConfig::lastUnexpectedTargetFileName);
                //重置
                AppConfig::lastUnexpectedFileName ="";
                AppConfig::lastUnexpectedTargetFileName ="";
                AppConfig::unexpectedFileSize = 0;
                AppConfig::unexpectedFilePos = 0;
                AppConfig::writeConfig();
            }
            return;
        }

        //按下暂停键,且数据处理完,才能暂停
        if(status == AppConfig::TransferState::Paused && arryList.isEmpty())
        {
            //暂停
            QThread::msleep(100);
            continue;
        }
        //防止出现新数据没到,但是旧数据已经处理完
        if(arryList.isEmpty())
            continue;
        QByteArray fileData = arryList.takeFirst();
        quint64 size = file.write(fileData,fileData.size());
        currentFileSize += size;
        int step = static_cast<int>((currentFileSize*100)/sourceFileSize) ;
        emit updatePgBar(step);
        QThread::msleep(100);
    }//while

    file.close();
    emit msgTips("写入完毕!");

    if(!AppConfig::lastUnexpectedTargetFileName.isEmpty())
    {
        QFile::remove(AppConfig::lastUnexpectedTargetFileName);
        //重置
        AppConfig::lastUnexpectedFileName ="";
        AppConfig::lastUnexpectedTargetFileName ="";
        AppConfig::unexpectedFileSize = 0;
        AppConfig::unexpectedFilePos = 0;
        AppConfig::writeConfig();
    }
}

你可能感兴趣的:(QT中级,qt,多线程,文件处理)