一个简单的Http下载软件,支持 断点续传 ,文件下载完成后可直接预览,不过下载速度一般(每次请求大约10K的数据量,也没有使用多线程切片下载)
下面的效果截图:
完整效果演示:
这里主要使用了使用了类 QNetworkAccessManager 、 QNetworkRequest 和 QNetworkReply 。具体实现关键部分如下:
核心代码如下:
// 创建请求
QNetworkRequest networkRequest;
networkRequest.setUrl(QUrl(url));
networkRequest.setRawHeader("User-Agent", "QtApp-HttpDownLoader");
networkRequest.setRawHeader("Accept", "application/octet-stream");
// head请求(获取文件大小)
m_pNetworkManager->head(networkRequest);
这里使用槽函数连接 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 表示请求的文件大小。
为了实现断点续传,这里使用Http协议中的 Range 首部字段,表明只需要部分资源的请求。
比如下图是我使用 Fiddler 抓包的首部信息:
发送的首部字段
表明请求资源的从 860244字节 到 870484字节的信息。
响应首部字段
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();
}
如果使用的是 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()));