QT练习Http通信代码下载器

37 篇文章 3 订阅
30 篇文章 2 订阅

要求:
实现从"http://code.tarena.com.cn/"下载代码
点击可以直接下载
QT版本:5.15

在这里插入图片描述
在这里插入图片描述



一、使用Qt Creater创建项目

工程名:HttpClient
类名:MainWindow(默认)
在这里插入图片描述
选择qmake构建
在这里插入图片描述
基类选择QMainWindow
在这里插入图片描述
创建完成后如下
在这里插入图片描述



二、工程文件.pro添加相应模块

添加网络模块:QT+=network
在这里插入图片描述



三、编写UI界面

要使用的控件有:Text Browser

  1. 双击mainwindow.ui打开
    在这里插入图片描述
  2. 添加Text Browser控件
    在这里插入图片描述
  3. 更改程序窗口名为:代码下载器
    在这里插入图片描述
  4. 控件TextBrowser去掉openlinks 选择
    默认是打开的。只能加载文档,加载不了网页。关闭后自己处理就可以了,
    在这里插入图片描述
  5. 布局
    这里选择垂直布局->报存
    在这里插入图片描述

四、头文件添加相关的类库和成员变量

  1. 添加头文件类库
    在这里插入图片描述
 //QT +=network
 #include <QNetworkAccessManager> //管理通信
 #include <QNetworkRequest>   //请求
 #include <QNetworkReply>   //响应
 #include <QUrl>          //网络地址
 #include <QAuthenticator> //处理登录认证 有些网站不需要认证,但是这个需要认证登录
 #include <QFile>       //文件操作
 #include <QFileInfo>     //文件信息
 #include <QDebug>     //打印调试

  1. 添加成员变量和槽函数
    在这里插入图片描述
public slots:
 //向服务器发送请求
 void sendRequest();
 //处理登录认证的槽函数
 void onAuthenticationRequired(QNetworkReply*,QAuthenticator*);
 //接收响应数据的槽函数
 void onReadyRead();
 //接收响应数据的槽函数
 void onFinished();

private:
    Ui::MainWindow *ui;
    QNetworkAccessManager *manager;  //管理HTTP通信的请求和响应
    QNetworkRequest request;     //请求
    QNetworkReply *reply;      //响应
    QUrl currentUrl;         //记录当前URL地址
    QByteArray buf;        //接收缓冲区:接收服务器返回的影响数据
};


五、源文件添加相应的槽函数

  1. 头文件中按F4跳转到源文件
    在这里插入图片描述
//向服务器发送请求
void MainWindow::sendRequest(){}

//处理登录认证的槽函数
void MainWindow::onAuthenticationRequired(QNetworkReply*,QAuthenticator*){}

//接收响应数据的槽函数
void MainWindow::onReadyRead(){}

//接收响应数据的槽函数
void MainWindow::onFinished(){}


六、代码编写

1. 构建函数创建完整对象

在这里插入图片描述

    //创建管理通信的Manger对象
    manager = new QNetworkAccessManager(this);
    //初始化请求URL
    request.setUrl(QUrl("http://code.tarena.com.cn/"));
    //发送请求
    sendRequest();


2. 编写发送请求sendRequest

在这里插入图片描述

//向服务器发送请求
void MainWindow::sendRequest(){
    //通过manager对象,使用GET方法向服务器发送请求,返回用于接收响应数据的reply对象指针
    reply = manager->get(request);
    //如果请求的URL,对应服务器需要登录认证,发送认证信号
   connect(manager,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
            this,SLOT(onAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
    //当服务器返回响应数据,reply将会发送readyRead信号,连接到接收响应的槽函数
    connect(reply,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
    //当接收响应数据完成,reply将会发送finished信号,连接接收响应数据结束的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(onFinished()));
}


3. 处理登录认证的槽函数

  1. 创建登录认证的窗口
    在这里插入图片描述
    在这里插入图片描述



    1. 选择没有任何按钮的对话框
      在这里插入图片描述
      在这里插入图片描述



    2. 创建完成后多了一个UI文件
      在这里插入图片描述



    3. 编辑logindialog.ui页面设置
      需要用到三个label、二个lineEdit、一个Button Box控件
      在这里插入图片描述



    4. 把输入框更改为明文输入
      在这里插入图片描述

    5. 修改LineEdit对象名
      在这里插入图片描述



    6. 确认和取消按钮转到槽函数自动生成
      在这里插入图片描述
      在这里插入图片描述



    7. 按F4转到logindialog头文件,添加成员变量
      在这里插入图片描述

        public:
            //获取用户名
            const QString& getUsername();
            //获取密码
            const QString& getPassword();   
        private:
            Ui::LoginDialog *ui;
            QString m_username;  //记录用户名
            QString m_password; //记录密码
    


    1. F4切换到源文件,添加对应槽函数
      在这里插入图片描述
       //确认按钮对应的槽函数
       void LoginDialog::on_buttonBox_accepted()
       {
           //从登录界面获取用户名和密码
           m_username = ui->usernameEdit->text();
           m_password = ui->passwdEdit->text();
           //退出登录窗口的事件循环,返回QDialog::Accepted
           accept();
       }
       
       //取消按钮对应的槽函数
       void LoginDialog::on_buttonBox_rejected()
       {
           //退出登录窗口的事件循环,返回QDialog::Accepted
           reject();
       }
       
       //获取用户名
       const QString& LoginDialog::getUsername(){
           return m_username;
       }
       
       //获取密码
       const QString& LoginDialog::getPassword(){
           return m_password;
       }
    
    1. 回到主页面代码添加头文件和实现登录槽函数
      在这里插入图片描述
      在这里插入图片描述
    //处理登录认证的槽函数
    void MainWindow::onAuthenticationRequired(QNetworkReply*,QAuthenticator* a){
        //创建登录认证的窗口
        LoginDialog login(this);
        //显示登录子窗口并进入事件循环,点击上面OK或者Cancel都会退出事件循环,但是返回值不同
        //如果点击Ok验证循环,点击Cancel不验证
        if(login.exec() == QDialog::Accepted){
            a->setUser(login.getUsername());
            a->setPassword(login.getPassword());
        }
    }
    


4. 接收响应数据的槽函数

在这里插入图片描述

//接收响应数据的槽函数
void MainWindow::onReadyRead(){
    //接收响应数据并保存
    buf +=reply->readAll();
    //记录当前URL地址
    currentUrl = reply->url();
}


5. 接收响应数据的槽函数

在这里插入图片描述

//接收响应数据的槽函数
void MainWindow::onFinished(){
    //显示
    ui->textBrowser->setText(buf);
    //清空buf
    buf.clear();
    //销毁reply对象
    reply->deleteLater();  //delete this;
}


七、运行测试

在这里插入图片描述
在这里插入图片描述



八、完善功能

1. MainWindow.h 头文件添加处理目录槽函数

在这里插入图片描述

    //处理目录链接的槽函数,参数为点击链接url地址
   void onAnchorclicked(const QUrl&url);


2.mainwindow.cpp修改构造函数

在这里插入图片描述

    //当接收响应数据完成,reply将会发送finished信号,连接接收响应数据结束的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(onFinished()));


3.添加相关槽函数

在这里插入图片描述

 //处理目录链接的槽函数,参数为点击链接url地址
 void MainWindow::onAnchorclicked(const QUrl & url){
      qDebug()<<"当前URL:"<<currentUrl.toString();
      qDebug() << "点击url:"<<url.toString();
      QUrl newUrl;  //要进入新的链接URL地址
      //如果点击时不是“../”
      if(url.toString() !="../"){
           //newUrl = 当前URL+点击URL
           newUrl = currentUrl.toString()+url.toString();
      }else{//处理../
           //如果当前在界面,点击../什么也不做
       if(currentUrl.toString() == "http://code.tarena.com.cn/"){
           return;
       }
       //如果不在首界面,点击“../”去掉最后一级链接路径
       //当前URL:http://code.tarena.com.cn/CSDCode/csd2002
       //点击 ../ :http://code.tarena.com.cn/CSDCode/
       //查找倒数第二次出现 "/"位置
       int pos = currentUrl.toString().lastIndexOf("/",-2);
       //字符串截断,截取到pos所在位置
       newUrl = currentUrl.toString().mid(0,pos+1);
       }
       
       //设置请求为新的url的
      request.setUrl(newUrl);
      //发送新的请求
      sendRequest();
  }


4.是否为文件做判断下载逻辑

  1. 回到源文件添加槽函数
    在这里插入图片描述
public slots:
    //向服务器发送请求
    void sendRequest();
    //处理登录认证的槽函数
    void onAuthenticationRequired(QNetworkReply*,QAuthenticator*);
    //接收响应数据的槽函数
    void onReadyRead();
   //接收响应数据的槽函数
    void onFinished();
    //处理目录链接的槽函数,参数为点击链接url地址
    void onAnchorclicked(const QUrl&url);
    //下载文件操作
    void downloadFile(const QUrl& url);
    //接收文件内容的槽函数
    void receiveFile();
    //打印当前下载进度槽函数,参数:已收到字节数/总字节数
    void onDownloadProgress(qint64,qint64);
    //接收文件内容结束执行的槽函数
    void receiveFileFinished();

private:
    Ui::MainWindow *ui;
    QNetworkAccessManager *manager;  //管理HTTP通信的请求和响应
    QNetworkRequest request;     //请求
    QNetworkReply *reply;      //响应
    QUrl currentUrl;         //记录当前URL地址
    QByteArray buf;        //接收缓冲区:接收服务器返回的影响数据
    QFile* file;         //保存要下载的文件
};


  1. 槽函数编写
    在这里插入图片描述
    在这里插入图片描述
//处理目录链接的槽函数,参数为点击链接url地址
void MainWindow::onAnchorclicked(const QUrl & url){
    //   qDebug()<<"当前URL:"<<currentUrl.toString();
   //   qDebug() << "点击url:"<<url.toString();
   QUrl newUrl;  //要进入新的链接URL地址
   //如果点击时不是“../”
   if(url.toString() !="../"){
       //newUrl = 当前URL+点击URL
       newUrl = currentUrl.toString()+url.toString();
   }else{//处理../
       //如果当前在界面,点击../什么也不做
       if(currentUrl.toString() == "http://code.tarena.com.cn/"){
           return;
       }
       //如果不在首界面,点击“../”去掉最后一级链接路径
       //当前URL:http://code.tarena.com.cn/CSDCode/csd2002
       //点击 ../ :http://code.tarena.com.cn/CSDCode/
       //查找倒数第二次出现 "/"位置
       int pos = currentUrl.toString().lastIndexOf("/",-2);
       //字符串截断,截取到pos所在位置
       newUrl = currentUrl.toString().mid(0,pos+1);
   }
    
   //判断newUrl是否为文件链接,如果是文件链接,则执行下载文件操作
   //判断方式:如果点击url不是目录链接,就是要下载的文件链接
   if(url.toString().lastIndexOf("/") ==-1){
         //qDebug()<<"下载文件操作:"<<newUrl;
         downloadFile(newUrl);
    }else{
   //设置请求为新的url的
   request.setUrl(newUrl);
   //发送新的请求
   sendRequest();
   }
 }

//下载文件
void MainWindow::downloadFile(const QUrl& fileUrl){
    //根据获取fileUrl获取文件名
    QFileInfo fileInfo = fileUrl.path();
    QString filename = fileInfo.fileName();
    //在本地创建同名的文件
    file = new QFile(filename,this);
    //打开本地同名文件,如果不存在将会自动创建
    file->open(QIODevice::WriteOnly);
    //设置请求URL为下载文件的URL
    request.setUrl(fileUrl);
    //发送下载文件连接请求
    reply = manager->get(request);
    //当收到服务器返回的响应数据(文件内容),发送信号readRead(),连接到接收文件内容的槽函数
    connect(reply,SIGNAL(readyRead()),this,SLOT(receiveFile()));
    //伴随文件下载REPLY会发送下载进度信号downloadProgress,连接到打印当前下载进度槽函数
    connect(reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(onDownloadProgress(qint64,qint64)));
    //文件下载完成,发送信号finished,连接到接收文件内容结束执行的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(receiveFileFinished()));
}
//接收文件内容的槽函数
void MainWindow::receiveFile(){
    //读取响应数据并写入本地同名文件
    file->write(reply->readAll());
}
//打印当前下载进度槽函数,参数:已收到字节数/总字节数
void MainWindow::onDownloadProgress(qint64 readBytes,qint64 totalBytes){
    //计算下载进度的百分比
    qint64 progress = readBytes*100/totalBytes;
    //打印进度
    qDebug()<<file->fileName()<<"下载:"<<progress<<"%...";
}
//接收文件内容结束执行的槽函数
void MainWindow::receiveFileFinished(){
    qDebug()<<file->fileName()<<":文件下载完成!";
    file->flush(); //刷新文件流
    file->close();
    reply->deleteLater();
}


5.测试

在这里插入图片描述



6.完整代码

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
//QT +=network
#include <QNetworkAccessManager> //管理通信
#include <QNetworkRequest>   //请求
#include <QNetworkReply>   //响应
#include <QUrl>          //网络地址
#include <QAuthenticator> //处理登录认证 有些网站不需要认证,但是这个需要认证登录
#include <QFile>       //文件操作
#include <QFileInfo>     //文件信息
#include <QDebug>     //打印调试


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public slots:
    //向服务器发送请求
    void sendRequest();
    //处理登录认证的槽函数
    void onAuthenticationRequired(QNetworkReply*,QAuthenticator*);
    //接收响应数据的槽函数
    void onReadyRead();
    //接收响应数据的槽函数
    void onFinished();
    //处理目录链接的槽函数,参数为点击链接url地址
    void onAnchorclicked(const QUrl&url);
    //下载文件操作
    void downloadFile(const QUrl& fileUrl);
    //接收文件内容的槽函数
    void receiveFile();
    //打印当前下载进度槽函数,参数:已收到字节数/总字节数
    void onDownloadProgress(qint64,qint64);
    //接收文件内容结束执行的槽函数
    void receiveFileFinished();

private:
    Ui::MainWindow *ui;
    QNetworkAccessManager *manager;  //管理HTTP通信的请求和响应
    QNetworkRequest request;     //请求
    QNetworkReply *reply;      //响应
    QUrl currentUrl;         //记录当前URL地址
    QByteArray buf;        //接收缓冲区:接收服务器返回的影响数据
    QFile* file;         //保存要下载的文件
};
#endif // MAINWINDOW_H


  1. mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "logindialog.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //创建管理通信的Manger对象
    manager = new QNetworkAccessManager(this);
    //初始化请求URL
    request.setUrl(QUrl("http://code.tarena.com.cn/"));
    //发送请求
    sendRequest();
    //点击界面的链接时,发送信号anchorclicked,
    connect(ui->textBrowser,SIGNAL(anchorClicked(QUrl)),this,SLOT(onAnchorclicked(QUrl)));
}

MainWindow::~MainWindow()
{
    delete ui;
}


//向服务器发送请求
void MainWindow::sendRequest(){
    //通过manager对象,使用GET方法向服务器发送请求,返回用于接收响应数据的reply对象指针
    reply = manager->get(request);
    //如果请求的URL,对应服务器需要登录认证,发送认证信号
    connect(manager,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
            this,SLOT(onAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
    //当服务器返回响应数据,reply将会发送readyRead信号,连接到接收响应的槽函数
    connect(reply,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
    //当接收响应数据完成,reply将会发送finished信号,连接接收响应数据结束的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(onFinished()));
}

//处理登录认证的槽函数
void MainWindow::onAuthenticationRequired(QNetworkReply*,QAuthenticator* a){
    //创建登录认证的窗口
    LoginDialog login(this);
    //显示登录子窗口并进入事件循环,点击上面OK或者Cancel都会退出事件循环,但是返回值不同
    //如果点击Ok验证循环,点击Cancel不验证
    if(login.exec() == QDialog::Accepted){
        a->setUser(login.getUsername());
        a->setPassword(login.getPassword());
    }
}

//接收响应数据的槽函数
void MainWindow::onReadyRead(){
    //接收响应数据并保存
    buf +=reply->readAll();
    //记录当前URL地址
    currentUrl = reply->url();
}

//接收响应数据的槽函数
void MainWindow::onFinished(){
    //显示
    ui->textBrowser->setText(buf);
    //清空buf
    buf.clear();
    //销毁reply对象
    reply->deleteLater();  //delete this;
}

//处理目录链接的槽函数,参数为点击链接url地址
void MainWindow::onAnchorclicked(const QUrl & url){
//   qDebug()<<"当前URL:"<<currentUrl.toString();
//   qDebug() << "点击url:"<<url.toString();
   QUrl newUrl;  //要进入新的链接URL地址
   //如果点击时不是“../”
   if(url.toString() !="../"){
       //newUrl = 当前URL+点击URL
       newUrl = currentUrl.toString()+url.toString();
   }else{//处理../
       //如果当前在界面,点击../什么也不做
       if(currentUrl.toString() == "http://code.tarena.com.cn/"){
           return;
       }
       //如果不在首界面,点击“../”去掉最后一级链接路径
       //当前URL:http://code.tarena.com.cn/CSDCode/csd2002
       //点击 ../ :http://code.tarena.com.cn/CSDCode/
       //查找倒数第二次出现 "/"位置
       int pos = currentUrl.toString().lastIndexOf("/",-2);
       //字符串截断,截取到pos所在位置
       newUrl = currentUrl.toString().mid(0,pos+1);
   }

   //判断newUrl是否为文件链接,如果是文件链接,则执行下载文件操作
   //判断方式:如果点击url不是目录链接,就是要下载的文件链接
   if(url.toString().lastIndexOf("/") ==-1){
       //qDebug()<<"下载文件操作:"<<newUrl;
       downloadFile(newUrl);
   }else{
   //设置请求为新的url的
   request.setUrl(newUrl);
   //发送新的请求
   sendRequest();
   }
}

//下载文件
void MainWindow::downloadFile(const QUrl& fileUrl){
    //根据获取fileUrl获取文件名
    QFileInfo fileInfo = fileUrl.path();
    QString filename = fileInfo.fileName();
    //在本地创建同名的文件
    file = new QFile(filename,this);
    //打开本地同名文件,如果不存在将会自动创建
    file->open(QIODevice::WriteOnly);
    //设置请求URL为下载文件的URL
    request.setUrl(fileUrl);
    //发送下载文件连接请求
    reply = manager->get(request);
    //当收到服务器返回的响应数据(文件内容),发送信号readRead(),连接到接收文件内容的槽函数
    connect(reply,SIGNAL(readyRead()),this,SLOT(receiveFile()));
    //伴随文件下载REPLY会发送下载进度信号downloadProgress,连接到打印当前下载进度槽函数
    connect(reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(onDownloadProgress(qint64,qint64)));
    //文件下载完成,发送信号finished,连接到接收文件内容结束执行的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(receiveFileFinished()));
}
//接收文件内容的槽函数
void MainWindow::receiveFile(){
    //读取响应数据并写入本地同名文件
    file->write(reply->readAll());
}
//打印当前下载进度槽函数,参数:已收到字节数/总字节数
void MainWindow::onDownloadProgress(qint64 readBytes,qint64 totalBytes){
    //计算下载进度的百分比
    qint64 progress = readBytes*100/totalBytes;
    //打印进度
    qDebug()<<file->fileName()<<"下载:"<<progress<<"%...";
}
//接收文件内容结束执行的槽函数
void MainWindow::receiveFileFinished(){
    qDebug()<<file->fileName()<<":文件下载完成!";
    file->flush(); //刷新文件流
    file->close();
    reply->deleteLater();
}


  1. logindialog.h
#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H

#include <QDialog>

namespace Ui {
class LoginDialog;
}

class LoginDialog : public QDialog
{
    Q_OBJECT

public:
    explicit LoginDialog(QWidget *parent = nullptr);
    ~LoginDialog();

private slots:
    //确认按钮对应的槽函数
    void on_buttonBox_accepted();
    //取消按钮对应的槽函数
    void on_buttonBox_rejected();

public:
    //获取用户名
    const QString& getUsername();
    //获取密码
    const QString& getPassword();

private:
    Ui::LoginDialog *ui;
    QString m_username;  //记录用户名
    QString m_password; //记录密码
};

#endif // LOGINDIALOG_H


  1. logindialog.cpp
#include "logindialog.h"
#include "ui_logindialog.h"

LoginDialog::LoginDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::LoginDialog)
{
    ui->setupUi(this);
}

LoginDialog::~LoginDialog()
{
    delete ui;
}

//确认按钮对应的槽函数
void LoginDialog::on_buttonBox_accepted()
{
    //从登录界面获取用户名和密码
    m_username = ui->usernameEdit->text();
    m_password = ui->passwdEdit->text();
    //退出登录窗口的事件循环,返回QDialog::Accepted
    accept();
}

//取消按钮对应的槽函数
void LoginDialog::on_buttonBox_rejected()
{
    //退出登录窗口的事件循环,返回QDialog::Accepted
    reject();
}


//获取用户名
const QString& LoginDialog::getUsername(){
    return m_username;
}

//获取密码
const QString& LoginDialog::getPassword(){
    return m_password;
}


  1. main.cpp
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}


九、版本2使用多线程

目前不足:一次只能下载一个文件,如果中途点了其他的就无法继续下载
扩展:

  • 使用多线程,将下载文件操作放到子线程中,实现多个文件同时下载
  • 将要下载的文件,指定保存在用户的Downloads目录下

完整代码

参考代码
mainwindow.h、 mainwindow.cpp、
登录验证:logindialog.h、logindialog.cpp
下载模块:download.h 、download.cpp



  1. 代码1:mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QAuthenticator>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
#include <download.h>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    //向服务器发送请求
    void sendRequest();
private slots:
    //处理登录认证的槽函数
    void onAuthenticationRequired(
            QNetworkReply*,QAuthenticator*);
    //接收响应数据的槽函数
    void onReadyRead();
    //接收响应数据完成时执行的槽函数
    void onFinished();
    //处理目录链接的槽函数
    void onAnchorClicked(const QUrl& url);

private:
    //下载文件
    void downloadFile(const QUrl& fileUrl);

private:
    Ui::MainWindow *ui;
public:
    QNetworkAccessManager* manager;//管理通信
private:
    QNetworkRequest request;//请求
    QNetworkReply* reply;//响应
    QUrl currentUrl;//记录当前的Url地址
    QByteArray buf;//保存接收的响应数据
};

#endif // MAINWINDOW_H


  1. 代码2:mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "logindialog.h"
#include "download.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //创建管理通信的manager对象
    manager = new QNetworkAccessManager(this);
    //初始化请求网址
    request.setUrl(QUrl("http://code.tarena.com.cn/"));
    //向服务器发送请求
    sendRequest();
    //点击界面的链接时,发送信号anchorClicked,
    //参数表示点击链接的URL地址
    connect(ui->textBrowser,SIGNAL(anchorClicked(QUrl)),
        this,SLOT(onAnchorClicked(QUrl)));
}
MainWindow::~MainWindow()
{
    delete ui;
}

//向服务器发送请求
void MainWindow::sendRequest()
{
    //发送请求时,禁用界面,避免连续发送请求而导致的异常结束
    ui->textBrowser->setEnabled(false);
    //向服务器发送请求
    reply = manager->get(request);
    //如果服务器需要进行登录认证,manager会发送认证
    //信号:authenticationRequired
    connect(manager,SIGNAL(
        authenticationRequired(
            QNetworkReply*,QAuthenticator*)),
        this,SLOT(onAuthenticationRequired(
            QNetworkReply*,QAuthenticator*)));
    //如果认证成功,响应数据到来,发送信号readyRead
    connect(reply,SIGNAL(readyRead()),
        this,SLOT(onReadyRead()));
    //响应数据接收结束,发送信号finished
    connect(reply,SIGNAL(finished()),
        this,SLOT(onFinished()));
}
//处理登录认证的槽函数
void MainWindow::onAuthenticationRequired(
        QNetworkReply*,QAuthenticator* authenticator)
{
    //qDebug("onAuthenticationRequired");
    //从登录认证子窗口中,获取用户名和密码在进行认证
    LoginDialog login(this);
    //显示登录窗口,并进入事件循环,点击上面Ok/Cancel
    //时都会退出登录窗口,但是返回值不同.
    //如果点击Ok按钮退出,返回QDialog::Accepted
    if(login.exec() == QDialog::Accepted){
        authenticator->setUser(login.getUsername());
        authenticator->setPassword(login.getPassword());
    }
}
//接收响应数据的槽函数
void MainWindow::onReadyRead()
{
    //qDebug("onReadyRead");
    //读取响应数据,并保存
    buf += reply->readAll();
    //保存当前URl地址
    currentUrl = reply->url();
}
//接收响应数据完成时执行的槽函数
void MainWindow::onFinished()
{
    //qDebug("onFinished");
    //显示响应数据
    ui->textBrowser->setText(buf);
    //清空buf
    buf.clear();
    //销毁reply对象
    reply->deleteLater();
    //恢复界面
    ui->textBrowser->setEnabled(true);
}

//处理目录链接的槽函数
void MainWindow::onAnchorClicked(
        const QUrl &url){
    //qDebug()<<"当前的URL:"<<currentUrl.toString();
    //qDebug()<<"点击的URL:"<<url.toString();

    QUrl newUrl;
    //如果点击是不是"../",新的URL=当前URL+点击URL
    if(url.toString() != "../"){
        newUrl = currentUrl.toString() +
                    url.toString();
    }
    //如果点击是"../"
    else{
        //如果当前在首页,什么也不做
        if(currentUrl.toString() ==
            "http://code.tarena.com.cn/"){
            return;
        }
        //如果不再首页,去掉最后一级链接路径
        //查找目录路径中倒数第二次出现"/"位置
        int pos = currentUrl.toString(
                    ).lastIndexOf("/",-2);
        //字符串截断,去掉后面的路径
        newUrl =
            currentUrl.toString().mid(0,pos+1);
    }
    //判断点击URL如果不是目录则执行文件下载操作.
    if(url.toString().lastIndexOf("/")==-1){
        downloadFile(newUrl);
        return;
    }
    //设置新的请求URL
    request.setUrl(newUrl);
    //发送新的请求
    sendRequest();
}

//下载文件
void MainWindow::downloadFile(const QUrl& fileUrl)
{
    //创建下载文件线程
    QThread *thread = new QThread;
    //创建下载文件对象
    DownloadFile* download = new DownloadFile(fileUrl);
    //将下载文件对象移动到子线程中
    download->moveToThread(thread);
    //下载文件完成时,让线程退出
    connect(download,SIGNAL(downloadFinished()),
            thread,SLOT(quit()));
    //线程结束时,删除下载文件的对象
    connect(thread,SIGNAL(finished()),
            download,SLOT(deleteLater()));
    //线程结束时,删除线程对象
    connect(thread,SIGNAL(finished()),
            thread,SLOT(deleteLater()));
    //设置下载文件请求的URL
    download->request.setUrl(fileUrl);

    //发送获取下载文件的请求
    download->reply = manager->get(download->request);

    //响应数据到来,在子线程中完成下载操作
    //download已经移动到子线程中,通过信号触发里面槽将在子线程中运行
    connect(download->reply,SIGNAL(readyRead()),
            download,SLOT(ReceiveFile()));
    connect(download->reply,SIGNAL(finished()),
            download,SLOT(ReceiveFileFinished()));
    connect(download->reply,SIGNAL(downloadProgress(qint64,qint64)),
            download,SLOT(onDownloadProgress(qint64,qint64)));
    //开启子线程
    thread->start();
}


  1. 代码3:logindialog.h
#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H

#include <QDialog>

namespace Ui {
class LoginDialog;
}

class LoginDialog : public QDialog
{
    Q_OBJECT

public:
    explicit LoginDialog(QWidget *parent = 0);
    ~LoginDialog();

private slots:
    //Ok按钮对应的槽函数
    void on_buttonBox_accepted();
    //Cancel按钮对应的槽函数
    void on_buttonBox_rejected();
public:
    //获取用户名
    const QString& getUsername();
    //获取密码
    const QString& getPassword();
private:
    Ui::LoginDialog *ui;
    QString username;//保存用户名
    QString password;//保存密码
};

#endif // LOGINDIALOG_H


  1. 代码4:logindialog.cpp
#include "logindialog.h"
#include "ui_logindialog.h"

LoginDialog::LoginDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::LoginDialog)
{
    ui->setupUi(this);
}

LoginDialog::~LoginDialog()
{
    delete ui;
}
//Ok按钮对应的槽函数
void LoginDialog::on_buttonBox_accepted()
{
    username = ui->usernameEdit->text();
    password = ui->passwordEdit->text();
    accept();//退出,返回QDialog::Accepted
}
//Cancel按钮对应的槽函数
void LoginDialog::on_buttonBox_rejected()
{
    reject();//退出,返回QDialog::Rejected
}
//获取用户名
const QString& LoginDialog::getUsername()
{
    return username;
}
//获取密码
const QString& LoginDialog::getPassword()
{
    return password;
}


  1. 代码5:download.h
#ifndef Download_H
#define Download_H

#include <QThread>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <mainwindow.h>

class DownloadFile : public QObject
{
    Q_OBJECT
public:
    //构造函数,参数表示要下载的文件连接
    DownloadFile(const QUrl& url);
    ~DownloadFile();
private slots:
    //接收文件的槽函数
    void ReceiveFile();
    //更新显示文件下载进度的槽函数
    //参数:已收到数据的字节数/总字节数
    void onDownloadProgress(qint64,qint64);
    //接收文件完成的槽函数
    void ReceiveFileFinished();

signals:
    //自定义信号,文件下载完成时发送
    void downloadFinished();
private:
    QNetworkRequest request;//请求
    QNetworkReply* reply;//响应
    QUrl fileUrl;//下载文件的URL地址
    QFile* file;

    //将MainWindow类声明为当前类的友元,友元类可以访问当前类的任何成员
    friend class MainWindow;
};

#endif // Download_H



  1. 代码6:download.cpp
#include "download.h"
#include "mainwindow.h"

DownloadFile::DownloadFile(const QUrl& url):fileUrl(url){
    //根据URL获取文件名
    QFileInfo fileInfo =QFileInfo(fileUrl.path());
    QString filename = fileInfo.fileName();

    //设置要下载文件的路径
    QDir dir = QDir::home();
    //判断是否存在Download目录
    if(dir.exists("Downloads")==false){
        //如果该目录不存在则创建
        if(dir.mkdir("Downloads") == false){
            qDebug("创建目录失败");
            return;
        }
    }
    //指定下载文件放到主目录的Download下面
    QString path = QDir::homePath() + "/Downloads/"+ filename;
    //在本地创建同名的文件
    file = new QFile(path);
    //以写的方式打开文件
    file->open(QIODevice::WriteOnly);
}
DownloadFile::~DownloadFile()
{
    //qDebug() << "~Download";
}
//接收文件的槽函数
void DownloadFile::ReceiveFile()
{
    if(reply->bytesAvailable()){
        file->write(reply->readAll());
    }
}
//参数:已收到数据的字节数/总字节数
void DownloadFile::onDownloadProgress(
        qint64 readBytes,qint64 totalBytes)
{
    qint64 progress=readBytes*100/totalBytes;//百分比
    qDebug() << file->fileName() << ":" << progress << "%....";
}
//接收文件完成的槽函数
void DownloadFile::ReceiveFileFinished()
{
    qDebug() << file->fileName() << "文件下载完成";
    file->flush();//刷新文件流
    file->close();//关闭文件
    delete file;//销毁文件对象
    reply->deleteLater();//销毁响应对象
    emit downloadFinished();//发送信号,表示文件下载完成
}


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值