基于Qt的简易Http下载软件

一个简单的Http下载软件,支持 断点续传 ,文件下载完成后可直接预览,不过下载速度一般(每次请求大约10K的数据量,也没有使用多线程切片下载)

下面的效果截图:

完整效果演示:

这里主要使用了使用了类 QNetworkAccessManagerQNetworkRequestQNetworkReply 。具体实现关键部分如下:


1. 发送head请求获取文件大小信息

核心代码如下:

// 创建请求
QNetworkRequest networkRequest;
networkRequest.setUrl(QUrl(url));
networkRequest.setRawHeader("User-Agent", "QtApp-HttpDownLoader");
networkRequest.setRawHeader("Accept", "application/octet-stream");

// head请求(获取文件大小)
m_pNetworkManager->head(networkRequest);
  • 这里发送的请求头设置首部字段 Acceptapplication/octet-stream 表示请求的数据为二进制数据。
  • m_pNetworkManagerQNetworkAccessManager 对象,这里发送的是 head 请求,只返回响应的首部信息。

这里使用槽函数连接 QNetworkAccessManagerfinished 信号,当 QNetworkAccessManager 处理完成服务器的响应后,发送此信号。

connect(m_pNetworkManager, &QNetworkAccessManager::finished, \
            this, &HttpDownLoader::onReplyFinished);

槽函数的处理关键代码如下:

// 获取文件大小
void HttpDownLoader::onReplyFinished(QNetworkReply* reply)
{
    QString lengthString = reply->rawHeader("Content-Length");
    if (lengthString.isEmpty())
    {
        emit error(tr("Failed to get file size."));
        return;
    }
    
    // 设置文件大小
    m_nTotalSize = lengthString.toInt();
}

响应的首部中,Content-Length 表示请求的文件大小。

注意 :这里没有做30X的重定型请求,比如请求有些URL并不是真正的下载地址,会返回30X,这时候需要自己处理。比如如果返回301,则需要在返回的首部中 Location 首部字段中获取真正的下载地址。

2. 发送获取部分文件请求

为了实现断点续传,这里使用Http协议中的 Range 首部字段,表明只需要部分资源的请求。
比如下图是我使用 Fiddler 抓包的首部信息:

发送的首部字段

Range: bytes=860244-870484

表明请求资源的从 860244字节 到 870484字节的信息。

响应首部字段

Content-Length: 10241
Content-Range: bytes 860244-870484/994356

Content-Length 表示真正响应的内容大小
Content-Range 响应的资源范围以及总数

发送请求代码如下:

QNetworkRequest networkRequest;
networkRequest.setUrl(QUrl(m_cUrlString));
networkRequest.setRawHeader("User-Agent", "QtApp-HttpDownLoader");
networkRequest.setRawHeader("Accept", "application/octet-stream");

// 设置获取文件的范围
QString rangeStr = "bytes=%1-%2";
int rangeRValue = m_nCurrentDownloadSize + m_nIntervalSize;
if (rangeRValue > m_nTotalSize)
    rangeRValue = m_nTotalSize;
rangeStr = rangeStr.arg(m_nCurrentDownloadSize)\
                   .arg(m_nCurrentDownloadSize + m_nIntervalSize);
networkRequest.setRawHeader("Range", rangeStr.toUtf8());

// 发送请求
QNetworkReply *reply = m_pNetworkManager->get(networkRequest);
connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), \
          this, SLOT(slotError(QNetworkReply::NetworkError)));

使用 get() 函数发送 get 请求。这里将响应的信号 readyRead() 和 槽函数连接 slotReadyRead() 。在槽函数中,主要实现文件数据的写入。当本次请求的数据全部相应完成时,QNetworkAccessManager 对象会发送 finished 信号。
下面是槽函数 slotReadyRead 的关键代码:

void HttpDownLoader::slotReadyRead(void)
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
    if (reply == nullptr)
        return;

    // 获取接收的数据
    QByteArray byteArray = reply->readAll();
    if (byteArray.size() <= 0)
    {
        m_status = t_Error;
        emit error(tr("Fail Recv File Data"));
        return;
    }
    m_nCurrentDownloadSize += byteArray.size();

    // 写入文件
    m_file->write(byteArray);
    m_file->flush();
}

需要值得注意的是,QNetworkAccessManager 对象发送的请求后返回的 QNetworkReply 对象,需要手动释放,一般使用在 finished 信号中使用 deleteLater() 函数。下面是 onReplyFinished 槽的完整代码,如果一次请求处理完毕后,根据条件判断是否需要继续发送资源请求。

void HttpDownLoader::onReplyFinished(QNetworkReply* reply)
{
    if (m_status == t_Start)
    {
        // 获取文件大小
        QString lengthString = reply->rawHeader("Content-Length");
        if (lengthString.isEmpty())
        {
            emit error(tr("Failed to get file size."));
            return;
        }

        // 设置文件大小
        m_nTotalSize = lengthString.toInt();
        emit readyWrite();
        return;
    }
    else
    {
        // 接收完成
        if (m_nTotalSize <= m_nCurrentDownloadSize)
        {
            m_file->close();
            m_status = t_Finished;
            emit downFinished();
            return;
        }

        // 继续发送
        requestFile();
    }

    reply->deleteLater();
}

3. 设置代理

如果使用的是 Fiddler 抓包软件,则需要设置代理,添加如下代码

// 设置代理
QNetworkProxy proxy;
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName("127.0.0.1");
proxy.setPort(8888);
m_pNetworkManager->setProxy(proxy);

当下载完成时,打开文件点击按钮打开文件夹,使用函数

QDesktopServices::openUrl(QUrl(info.absolutePath()));
不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:Qt 实现上下滚动字幕