进阶
上文的Http客户端只能下载指定网址的数据,这样的客户端在交互性和功能性上都很差。本文所描述的程序则在这个基本的客户端上进行改造,实现任意目标地址的数据下载,并且改善了用户界面的。具体UI可参考下图:
对于UI而言,该客户端增加了任意地址的输入框、下载进度条和下载按钮;对于下载的数据而言,该客户端不再局限于下载文本数据。接下来本文将按照程序执行的大致顺序对相关函数进行分析。
构造函数
用户界面的设计可见本文参考1和参考2,这里从构造函数开始。在构造函数中除了对用户界面进行初始化外,还创建了一个QNetworkAccessManager对象,并将进度显示条隐藏了起来。
widget::widget(QWidget *parent) : QWidget(parent), ui(new Ui::widget) { ui->setupUi(this); manager = new QNetworkAccessManager(this); ui->progressBar->hide(); }
on_pushButton_clicked槽函数
该槽函数对应着下载按钮的单击事件。该函数完成的工作有从用户界面获得下载地址;从这个地址中解析出所下载文件的名称;打开(创建)所下载的文件;发送下载请求;更新进度显示条的信息。
从上文中可以看到,在Qt中使用QUrl类来存储url地址,通过该类的成员函数还可以根据具体情况对url地址进行相应的处理。使用QFileInfo类的对象存储不依赖具体系统的文件属性,比如文件的名称,路径,访问权限等;通过该类的成员函数可以方便的获取文件的某些属性,比如通过fileName成员函数就可以从路径名中快速解析出文件名。
得到了文件名就可以创建一个QFile类的对象,通过open成员函数就可以打开这个文件,QIODevice::WriteOnly为打开模式。如果打开错误,则弹出警告提示框,并进行相应的错误处理。
文件打开成功后,紧接着就应该发送下载链接的请求了,这个工作将在startRequest()中完成。最后设置进度更新条的初始值,并将其在界面上显示出来。具体的实现代码可参考下图:
void widget::on_pushButton_clicked() { url.setUrl(ui->lineEdit->text()); QFileInfo info(url.path());//获得地址; QString fileName(info.fileName());//从地址中获得文件名; //如果地址类似www.edsionte.com,则文件名为index.html; if (fileName.isEmpty()) fileName = "index.html"; file = new QFile(fileName); if (!file->open(QIODevice::WriteOnly)) { QMessageBox::warning(this, tr("Warning"), tr("file open error"), QMessageBox::Yes); qDebug() << "file open error"; delete file; file = 0; return; } startRequest(url);//进行请求; ui->progressBar->setValue(0); ui->progressBar->show(); }
startRequest()的实现
通过参考下面的示例代码就可以发现,该函数注意进行了两部分的内容:发送下载请求并获得数据回复和几组信号和槽函数之间的连接。
get()函数在上文中已有说明,它将返回一个QNetworkReply类的对象reply。当所有的数据下载完毕后,manager对象将发送finished信号,进而调用httpFinished槽函数。
上文所描述的Http客户端是等待所有的数据都下载到内存再读出并显示,而本文所描述的Http客户端则将请求的数据进行分段下载并保存。因此每当有一部分新数据到达本地,reply就会发送readyRead信号,进而调用httpReadyRead槽函数将这部分新的数据保存到本地。使用这种分段下载并保存数据的方法可以有效的节省内存。而一旦网络请求有数据返回,reply对象就会发送downloadProgress信号,进而引发updataReadProcess槽函数对进度显示条进行更新。
void widget::startRequest(QUrl url) { reply = manager->get(QNetworkRequest(url)); connect(reply, SIGNAL(finished()), this, SLOT(httpFinished())); connect(reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead())); connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updataReadProcess(qint64,qint64))); }
相关槽函数
当有一部分新数据返回到本地后,就回执行httpReadyRead函数。该函数将这部分新到达内存的数据写入file对象所对应的文件中。
void widget::httpReadyRead() { //如果文件存在,则将数据写入文件; if (file) file->write(reply->readAll()); }
每当请求的数据有返回时,就执行updataReadProcess函数以便及时更新进度显示条。该函数包含两个参数byteRead和totalBytes。前者表示当前接受到的数据总量,后者表示应该下载的数据总量。显然,随着byteRead的增加,进度显示条也不断更新。当byteRead和totalBytes相等时,表示下载完毕。
void widget::updataReadProcess(qint64 byteRead, qint64 totalBytes) { ui->progressBar->setMaximum(totalBytes);//最大值; ui->progressBar->setValue(byteRead);//当前值 }
当所有数据都下载完毕后,就执行httpFinished函数。在该函数中将隐藏进度显示条,并将缓冲区的数据清空。并且关闭文件对象,再释放之前申请的一些数据空间。
void widget::httpFinished() { ui->progressBar->hide(); file->flush(); file->close(); reply->deleteLater(); reply = 0; delete file; file = 0; }
至此,基本上完成了一个Http下载客户端。理论上可以下载任何数据,不过我在测试中发现有些地址不能如期下载(比如QQ的下载链接)。
参考:
1. Qt Assistant
2. http://www.yafeilinux.com/?p=734
3. C++ GUI Qt 4编程(第二版); 电子工业出版社;Blanchette,J,Summerfield,M 著;闫锋欣 等译;