一个简单的Http下载软件,支持 断点续传 ,文件下载完成后可直接预览,不过下载速度一般(每次请求大约10K的数据量,也没有使用多线程切片下载)
个人博客主页:不会飞的纸飞机
文章同步页(可下载完整代码):基于Qt的简易Http下载软件
可执行文件下载:点击下载
下面的效果截图:
完整效果演示:
这里主要使用了使用了类 QNetworkAccessManager 、 QNetworkRequest 和 QNetworkReply 。具体实现关键部分如下:
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);
- 这里发送的请求头设置首部字段 Accept 为 application/octet-stream 表示请求的数据为二进制数据。
- m_pNetworkManager 是 QNetworkAccessManager 对象,这里发送的是 head 请求,只返回响应的首部信息。
这里使用槽函数连接 QNetworkAccessManager 的 finished 信号,当 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()));
测试用下载链接:
音频文件
https://www.douzhq.cn/wp-content/uploads/2020/02/丫蛋蛋-大田後生仔.mp3
https://www.douzhq.cn/wp-content/uploads/2020/02/宝石Gem-野狼disco.mp3
图片
https://www.douzhq.cn/wp-content/uploads/2020/01/memory_pool_headImage.jpg
https://www.douzhq.cn/wp-content/uploads/2020/02/thread_create.jpg
https://www.douzhq.cn/wp-content/uploads/2020/02/downLoadTestPix-scaled.jpg