目录
开发过程中遇到了一些坑,花了半天时间搞。出现了很多理论上不应该出现的bug。
做这个的目的,主要是为了巩固一下对Qt网络部分接口的使用,以及http协议的记忆。
思路主要是,解析URL——>获取响应头——>获取响应状态码——>请求下载——>回读当前文件字节数进行断点续传。
需要注意,访问https前,需要添加libcrypto-1_1.dll与libssl-1_1.dll到Qt\5.14.0\mingw73_32\bin或者exe目录下。
以上DLL下载地址:http://slproweb.com/products/Win32OpenSSL.html
需要注意一下32位和64位,应下载与自己Qt编译器位数一致的版本。下载好后直接安装,然后到安装目录拷贝即可。
一、gitHub地址
https://github.com/KindMans/HttpDownLoad
二、功能
支持输入url网址进行在线下载,具备断点续传功能。
后期扩展:结合qml树列表与多线程,模拟出迅雷的下载效果。
三、目前存在的问题
QEventLoop *loop = new QEventLoop;
connect(m_netWorkManager, SIGNAL(finished(QNetworkReply*)), loop, SLOT(quit()));
m_reply = m_netWorkManager->get(request);
loop->exec();
通过以上get访问到需要跳转的url时,速度明显快于不需要跳转的。具体原因还不清楚。
四、界面效果
粗略的绘制了一下界面,比较丑,主要以实现效果为主。
五、主要代码
启动下载:
void Http::startDownLoad(const QString &url)
{
m_url = url;
if(m_url.isEmpty()) return;
if(!m_IsDownloading)
{
//获取请求头
QNetworkRequest request;
QUrl url = QUrl(m_url);
request.setUrl(url);
m_fileName = url.fileName();
qDebug()<<"fileName = "<<m_fileName;
m_reply = m_netWorkManager->head(request);
m_state = requestHead;
getCurrentFileSize();
connect(m_reply,SIGNAL(finished()),this,SLOT(onfinishedRequest()));
}
}
通过setUrl来装载我们请求的地址,随后获取请求头,请求结束后,进入槽函数做处理。
暂停下载:
void Http::stopDownLoad()
{
if(m_reply == nullptr) return;
disconnect(m_reply,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
disconnect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(onError(QNetworkReply::NetworkError)));
disconnect(m_reply,SIGNAL(finished()),this,SLOT(onfinishedRequest()));
m_reply->abort();
m_reply->deleteLater();
m_file.close();
getCurrentFileSize();
m_IsDownloading = false;
}
暂停下载主要是断开信号槽的连接,然后释放资源和关闭文件操作符。
文件大小回读:
void Http::getCurrentFileSize()
{
QFileInfo fileInfo(m_fileName);
if(fileInfo.exists())
{
m_currentLoadedBytes = fileInfo.size();
}
else
{
m_currentLoadedBytes = 0;
}
}
通过QFileInfo类提供的size()接口,获取文件当前的字节大小。
槽函数onfinishedRequest():
void Http::onfinishedRequest()
{
if(m_reply==nullptr) return;
if(m_state == requestHead)
{
m_fileSize = m_reply->rawHeader("Content-Length").toInt();
qDebug()<<"m_fileSize = "<<m_fileSize;
if(m_currentLoadedBytes == m_fileSize)
{
qDebug()<<"文件已经存在!";
return;
}
QEventLoop *loop = new QEventLoop;
connect(m_netWorkManager, SIGNAL(finished(QNetworkReply*)), loop, SLOT(quit()));
QNetworkRequest request;
request.setUrl(m_url);
m_reply = m_netWorkManager->get(request);
loop->exec();
//获取状态码
m_statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug()<<"statusCode="<<m_statusCode;
m_state = requestBody;
}
QNetworkRequest request;
if(m_statusCode==200)
{
request.setUrl(m_url);
}
else if(m_statusCode == 302) //存在转调url
{
//获取实际下载地址
QUrl realUrl = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
request.setUrl(realUrl);
}
else
{
return;
}
QString downLoadSize = QString::number(m_fileSize);
QString selectSize = QString("bytes=%1-%2").arg(m_currentLoadedBytes).arg(downLoadSize);
request.setRawHeader("Range",selectSize.toLatin1());
m_reply = m_netWorkManager->get(request);
connect(m_reply,SIGNAL(finished()),this,SLOT(onfinishedRequest()));
connect(m_reply,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(onError(QNetworkReply::NetworkError)));
}
这里需要注意的是做了一个转调功能,因为有些资源的实际下载地址并不是当前所给的Url。当返回302的时候,说明需要获取背后正确的下载地址。
槽函数onReadyRead():
void Http::onReadyRead()
{
if(m_reply==nullptr) return;
if(!m_file.isOpen())
{
m_file.setFileName(m_fileName);
m_file.open(QIODevice::WriteOnly|QIODevice::Append);
}
m_file.write(m_reply->readAll());
m_downLoadedBytes =m_file.size();
emit fileDownloadProgress(m_downLoadedBytes, m_fileSize);
if(m_file.size() == m_fileSize)
{
qDebug()<<"download finished!";
stopDownLoad();
}
}
将接收到的数据,写入文件中,并且将当前下载字节数通过信号槽发送出去,提供给进度条对话框使用。
当下载的字节总大小等于请求时服务器返回过来的文件大小时,就认为下载结束。
当然,更严谨的做法是再计算文件的MD5码,与服务器回传的MD5码进行比较,以确保下载的是正确的文件。