QT环境下的多线程下载实现

来源:互联网 发布:同步带长度计算软件 编辑:程序博客网 时间:2024/05/27 19:26

QT环境下的多线程下载一般有两种实现方式:一种是采用QNetworkAccessManager的基础上,自己写下载代码,此方案的好处是什么都可以自己控制,缺点是下载速度慢、编码工作量大;第二种是使用QProcess调用外部程序实现,好处是直接调用成熟的第三方下载工具,下载速度快,代码量少。本文主要介绍第二种方式实现,调用Axel进行下载。之所以选择Axel,原则在于Axel的优点就是参数简单,多线程,下载速度快,尤其是下载大文件优势明显。

代码实现思路:初始化5个QProcess做为下载进程,组成一个简单的下载池。当需要下载文件时,检查下载池是否有QProcess下载进程空闲,如果有,就调用进行下载,同时如果没有,就保存到等待下载列表中。每个下载进程会在下载完成后发出void finished(int exitCode, QProcess::ExitStatus exitStatus)信号。程序接收到这个信号后,发出“已经下好一个文件”的信号通知别的进程,同时检查下载列表是否为空,如果不为空,就重新检查是否有空闲的下载进程,如果有,则下载。


核心代码见下面。

#include "dsdownload.h"#include "common/dsconfig.h"#include "common/dscommon.h"#include "net/dsnetwork.h"#include <QApplication>#include <QFile>DsDownload::DsDownload(QObject *parent) :    QObject(parent){    for(int i = 0 ; i < 5; i++) {        //初始下载任务池大小为5        QProcess *p = new QProcess(this);        connect(p, SIGNAL(finished(int,QProcess::ExitStatus)),                this, SLOT(downloadProcessFinished(int, QProcess::ExitStatus)));        pPool.append(p);    }//为避免阻塞,使用信号的方式通知下载进程进行下载    connect(this, SIGNAL(needStartDownloadFile(QString)), this, SLOT(startDownloadFile(QString)));    connect(this, SIGNAL(finishedDownloadFile(QString)), this, SLOT(checkNeedDownloadFileSlot()));}//下载参数,-n 2:同时2个线程;-o %1:保存目录,%1会下面被替换; %2:下载的链接const QString DsDownload::axelCmdBase = "axel -n 2 -o %1 %2";/***下载实现*/void DsDownload::startDownloadFile(QString filename) {    log.debug(Q_FUNC_INFO, tr("start... download file: %1").arg(filename));    QString downUrl = this->needDownloadFileMap.value(filename);//为避免进程可能被占用,重新取一次    QProcess *p = NULL;    int i = 0;    foreach (QProcess *pp, pPool) {        if(pp->state() == QProcess::NotRunning) {            log.debug(Q_FUNC_INFO, tr("download pool index: %1").arg(i));            p = pp;            break;        }        ++i;    }    if(p != NULL && p->state() == QProcess::NotRunning) {        QString axelCmd(tr(DsDownload::axelCmdBase.toAscii())                        .arg("savePath", downUrl));        log.debug(Q_FUNC_INFO, "axelCmd: " + axelCmd);        p->start(axelCmd);        //等待开始        if(! p->waitForStarted()) {            log.error(Q_FUNC_INFO, tr("Start process failed  when download file: %1").arg(axelCmd));            return;        }//保存文件和下载进程的关系,以便下载完成后,更新数据库使用        pMap.insert(filename, p);    } else {    //如果没有下载进程空闲,就把文件重新保存到下载列表中    //因为上面使用的是:QString filename = needDownloadFileList.takeFirst();        this->needDownloadFileList.insert(0, filename);    }}/***下载进程完成后,发会出void finished(int exitCode, QProcess::ExitStatus exitStatus)信号*这里对应的接收SLOT*/void DsDownload::downloadProcessFinished ( int exitCode, QProcess::ExitStatus exitStatus ) {    log.debug(Q_FUNC_INFO, tr("一个下载进程结束. exitCode: %1, exitStatus: %2")              .arg(exitCode)              .arg(exitStatus));//因为不知道是哪个下载进程结束,所以只好循环查询“文件-进程”对应的MAP    QMapIterator<QString, QProcess *> i(pMap);    while (i.hasNext()) {        i.next();        QString filename = i.key();        QProcess *p = i.value();        QString filename = this->needDownloadFileMap.value(filename);        //通过检查进程的状态,查看下载是否结束,还可以通过判断下载文件的临时文件是否存在来判断(axel会产生".st"结尾的临时文件)             if (p->state() != QProcess::NotRunning) {                        continue;        }               log.debug(Q_FUNC_INFO, tr("file downloaded: %1")                  .arg(filename));        //移除文件-下载进程的对应关系                  pMap.remove(filename);//移除文件-下载地址的对应关系        this->needDownloadFileMap.remove(filename);        QVariantList pList;        pList.append(filename);        //更新数据库中文件的状态为2:表示已经下载完成        db.doPrepare("update resource set status=2 where filename=?", pList);        //发出下载完成的信号,一是由其它程序去处理(比如是否开始播放)        //二是让程序检查是否还有需要下载的文件        emit this->finishedDownloadFile(filename);    }    log.debug(Q_FUNC_INFO, tr("finished"));}


以上的代码,实现了下载列表分开(保存到数据库),进程池调用,由第三方下载工具axel确保下载速度,同时下载完后会第一时间发出通知,不会造成阻塞。

以前在网上参考过一段代码,它的实现是使用循环来等待程序结束,300ms让Ui响应一次,这样会导致前台展示出现非常明显的卡顿现象,影响用户体验。经过排查发现,平时CPU占用一般在30%左右,下载时CPU只占用2%左右,也就是说,下载进程阻塞了前台的UI进程。贴出以前参考的代码,供对比。

bool enstCdRecord::CreateCd(const QString &pImageFile) {QStringList cmdlist;cmdlist.append("-v");cmdlist.append("speed=2");cmdlist.append(pImageFile);mProcess.start("cdrecord.exe", cmdlist);while (! mProcess.waitForFinished(300)) { //启动程序后,用循环等待其结束,如果对程序何时结束并不关心,以下代码可以不需要。if (mProcess.state() == QProcess::NotRunning) ...{ //process failed    QMessageBox::critical(mParent, SYSTEMNAME, tr("Error when record cd."));    return false;}qApp->processEvents(); //防止UI死锁,一般情况下,用这种等一小段时间(这里是300ms),让UI响应一次的办法,已经足够使用了。}if (mProcess.exitCode() != 0) { //error when run process    QMessageBox::critical(mParent, SYSTEMNAME, tr("Error when record cd."));    return false;}return true;}


 


原创粉丝点击