http://foolog.net/?p=2157
在Qt中,提供了QNetworkAccessManager这个类,用于完成基于Http协议的数据上传和下载,该类既可以发送网络请求,也可以接受网络回复。而具体的网络请求是通过QNetworkRequest类发送的,具体的网络回复是通过QNetworkReply类来接收的。
本文将利用上面提到的几个类实现使用Http协议,获取指定的页面,并说明如何向该页面传递POST参数,最后在此基础上添加一个进度条,用于检测页面文件读取进度。
基本原理
由于QNetworkAccessManager类中包含了一组标准的数据请求函数,因此可以通过该类的对象发送数据请求函数;每个请求函数执行完毕时都回返回一个QNetworkReply对象。当所有请求的数据都到达本地后,将引发一个finished()信号,该信号关联了一个处理返回数据的槽函数。具体的实现可参考下述代码:
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //新建QNetworkAccessManager对象 manager = new QNetworkAccessManager(this); //关联信号和槽 connect(manager,SIGNAL(finished(QNetworkReply*)), this,SLOT(replyFinished(QNetworkReply*))); //发送请求 manager->get(QNetworkRequest(QUrl("http://wap.foolog.sinaapp.com"))); }
由于上面的这些类属于网络模块,所以需要在工程文件.pro中添加下面的语句,表明我们使用了网络模块:
QT += network
另外,还需要在有文件中添加包含头文件:
1
|
#include <QtNetwork> |
可以看到,上述的基本原理大部分都在构造函数中完成。首先创建了一个QNetworkAccessManager对象manager;接着将manager所引发的finished()信号与replyFinished()槽进行关联;最后通过get()发送数据请求。
get()用于发送请求并获得目标地址中的数据,具体的数据请求则是通过创建一个QNetworkRequest类的对象而完成的。只要数据请求发送成功,则开始下载数据。当所有的数据下载完成后,就返回一个QNetworkReply类型的对象。同时manager对象将发送一个finished()信号,引发replyFinished槽函数的执行。
当执行上述的槽函数时,就说明目标地址的数据已经下载完毕。此时槽函数要做的就是将这些数据显示出来。这里我们只对文本数据进行转换。对这些数据的转换动作可参考下述的代码:
void Widget::replyFinished(QNetworkReply *reply) { //使用utf8编码,这样才可以显示中文 QTextCodec *codec = QTextCodec::codecForName("utf8"); QString all = codec->toUnicode(reply->readAll()); ui->textBrowser->setText(all); reply->deleteLater(); //最后要释放reply对象 }
为了能够正确显示中文,我们创建QTextCodec对象。利用readAll函数可以读取数据请求返回的所有数据,并且利用toUnicode函数将这些数据转换成QString类型。最后在用户界面中的TextBrower控件中显示出来。
按照上面的方法就可以下载指定地址的数据。如下图:
当返回的数据显示完毕后,利用deleteLater函数将返回的数据删除。
发送Post请求
上面的获取指定页面的方式是采用get的方式获取的,如果想通过post的方式传递参数,需要自己构造HTTP请求头部,在上面的代码中,将原有的构造函数代码修改成下面的代码即可。
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //参数 QByteArray content = "name=user&pwd=123"; int contentLength = content.length(); //构造请求 QNetworkRequest req; req.setUrl(QUrl("http://localhost/login.php")); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); req.setHeader(QNetworkRequest::ContentLengthHeader,contentLength); //新建QNetworkAccessManager对象 manager = new QNetworkAccessManager(this); //关联信号和槽 connect(manager,SIGNAL(finished(QNetworkReply*)), this,SLOT(replyFinished(QNetworkReply*))); //以Post方式请求页面 manager->post(req,content); }
在上面的程序中,以post的方式请求指定页面,如果获取成功了,则会调用replyFinished这个槽函数,该函数的处理过程还是和原先的一样。
监控下载文件进度
现在,我们使用上面提到的这些类下载网上文件,一般我们下载文件都想要看到下载进度,所以现在,我们需要给请求文件的过程添加进度条,显示当前下载的进度。
界面布局
现在在界面上,需要放置一个ProcessBar控件,用于显示获取页面或文件下载的进度,还要有一个LineEdit控件,用于获取页面URL,页面布局如下:
类的头文件
widget.h头文件中代码:
class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); void startRequest(QUrl url); //请求链接 ~Widget(); private: Ui::Widget *ui; QNetworkAccessManager *manager; QNetworkReply *reply; QUrl url; //存储网络地址 private slots: //下载按钮的单击事件槽函数 void on_pushButton_clicked(); //完成下载后的处理 void httpFinished(); //接收到数据时的处理 void httpReadyRead(); //更新进度条 void updateDataReadProgress(qint64,qint64); };
构造函数
然后在构造函数中初始化manager变量,以及隐藏进度条。
manager = new QNetworkAccessManager(this); ui->progressBar->hide();
单击下载按钮处理函数
当点击按钮时,开始加载指定的页面,单击按钮信号的处理槽函数如下:
void Widget::on_pushButton_clicked() { url = ui->lineEdit->text(); QFileInfo info(url.path()); QString fileName(info.fileName()); //获取文件名 if (fileName.isEmpty()) { //如果文件名为空,则使用“index.html”, fileName = "index.html"; } file = new QFile(fileName); if(!file->open(QIODevice::WriteOnly)) { //如果打开文件失败,则删除file,并使file指针为0,然后返回 qDebug() << "file open error"; delete file; file = 0; return; } //进行链接请求 startRequest(url); //进度条的值设为0 ui->progressBar->setValue(0); //显示进度条 ui->progressBar->show(); }
这里先从界面中获取输入的地址,然后分解出文件名。因为地址中可能没有文件名(比如输入www.foolog.sinaapp.com),这时我们就使用一个默认的文件名。然后我们用这个文件名新建一个文件,然后我们以写入方式打开文件。最后进行链接,并显示进度条。
链接请求函数
链接请求函数startRequest代码如下:
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(updateDataReadProgress(qint64,qint64))); }
上面程序中的manager->get(QNetworkRequest(url))语句返回的是一个QNetworkReply对象,这里我们获得这个对象,使用它完成显示数据下载进度的功能。这里主要是关联了几个信号和槽。当有可用数据时,reply就会发出readyRead()信号,我们这时就可以将可用的数据保存下来。就是在这里,实现了数据分段下载保存,这样下载完所有数据再保存,要节省很多内存。而利用reply的downloadProgress()信号,很容易就实现了进度条的显示。
保存文件
当有可用数据时,reply就会发出readyRead()信号,我们这时就可以将可用的数据保存下来,相应的槽函数如下:
void Widget::httpReadyRead() { if (file) { //如果文件存在,则写入文件 file->write(reply->readAll()); } }
这里当file可用时,将下载的数据写入文件。
更新进度条函数
每当有数据到来时,都更新进度条。
void Widget::updateDataReadProgress(qint64 bytesRead, qint64 totalBytes) { ui->progressBar->setMaximum(totalBytes); //最大值 ui->progressBar->setValue(bytesRead); //当前值 }
完成下载
void Widget::httpFinished() //完成下载 { ui->progressBar->hide(); file->flush(); file->close(); reply->deleteLater(); reply = 0; delete file; file = 0; }
这里只是当下载完成后,进行一些处理。
最后的程序运行结果如下所示: