Qt 网络编程 网络下载

gemini:

在 Qt 的网络模块中,如果说 QTcpSocket 是让你亲自去铺设电缆、处理字节流的"底层苦工",那么 QNetworkAccessManager 及其相关类就是一套高级的现代化物流系统

为了让你生动形象地理解这"网络三剑客",我们可以把网络请求的过程比作一次 "快递寄送与签收" 的全过程。

📦 生动形象的"物流系统"比喻

想象一下,你坐在家里(你的应用程序),想要从远方的仓库(服务器)获取一件商品(下载数据)

1. QNetworkAccessManager ------ "物流调度总站"
  • 角色 :这是整个系统的核心大脑 ,也就是调度员

  • 作用:你不需要亲自去跑腿,也不需要关心路怎么走(是 HTTP 还是 FTP,是否有代理)。你只需要把你的需求告诉这位"调度员",它会负责指派卡车、安排路线、处理并发,并最终通知你"货到了"。

  • 特点 :通常一个应用程序只需要一个这样的调度员(单例模式使用最常见)。

2. QNetworkRequest ------ "运单/需求单"
  • 角色 :这是你填写的寄件单

  • 作用:这张单子上写着:

    • 地址 (URL) :货在哪个仓库?(例如 http://www.google.com

    • 备注 (Headers) :这批货是什么类型的?我是谁?(例如 User-Agent, Content-Type)。

  • 特点:它只是一个信息的载体,本身不干活,只负责承载信息给调度员看。

3. QNetworkReply ------ "包裹/回执"
  • 角色 :这是调度员派出去处理完任务后,给你的反馈对象

  • 作用 :注意,它不仅包含包裹里的东西 (服务器返回的数据),还包含物流状态(状态码 200 OK 还是 404 Not Found)。

  • 特点 :它是一个 I/O 设备(继承自 QIODevice),你可以像读取文件一样读取它。同时,它会实时告诉你进度(downloadProgress 信号),就像物流信息的实时更新。

🛠️ 详细拆解与分工

1. QNetworkAccessManager (QNAM)

它是发起网络请求的入口。

  • 关键方法

    • get(request): 发起一个下载请求(GET)。

    • post(request, data): 发起一个上传/提交请求(POST)。

  • 关键信号

    • finished(QNetworkReply*): 只要有任何请求结束了(无论成功失败),这个大喇叭就会广播。
2. QNetworkRequest

它是请求的配置容器。

  • 设置地址setUrl(QUrl("https://..."))

  • 设置头信息setRawHeader("Content-Type", "application/json")

3. QNetworkReply

它是请求的结果,也是数据流。

  • 获取数据readAll() (因为它是 QIODevice)。

  • 检查错误error()errorString()

  • 获取响应头rawHeader()

  • 生命周期极其重要! 它是动态创建的对象,用完必须由你(或者设置自动删除)来清理,否则会内存泄漏。

💻 简单的使用方法 (代码示例)

下面是一个经典的HTTP GET 下载示例。我们将创建一个简单的类来演示如何下载一个网页的 HTML 内容。

场景:下载百度首页的 HTML
cpp 复制代码
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QDebug>
#include <QObject>

class Downloader : public QObject {
    Q_OBJECT
public:
    Downloader(QObject *parent = nullptr) : QObject(parent) {
        // 1. 创建"物流调度员" (QNetworkAccessManager)
        manager = new QNetworkAccessManager(this);

        // 连接信号:当请求完成时,调用我们的槽函数
        connect(manager, &QNetworkAccessManager::finished,
                this, &Downloader::onResult);
    }

    void doDownload() {
        // 2. 填写"运单" (QNetworkRequest)
        QUrl url("http://www.baidu.com");
        QNetworkRequest request(url);
        
        // 伪装成浏览器(可选,防止被某些服务器拒绝)
        request.setRawHeader("User-Agent", "MyQtApp 1.0");

        qDebug() << "开始请求:" << url.toString();

        // 3. 交给调度员,并拿到"包裹凭证" (QNetworkReply)
        // 注意:这里是异步的!代码会立刻往下执行,不会卡在这里等下载。
        manager->get(request); 
    }

public slots:
    // 4. 处理"到货" (Slot)
    void onResult(QNetworkReply *reply) {
        // 检查是否有错误
        if (reply->error() != QNetworkReply::NoError) {
            qDebug() << "下载出错:" << reply->errorString();
        } else {
            // 获取 HTTP 状态码 (例如 200)
            int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
            qDebug() << "状态码:" << statusCode;

            // 5. 拆包裹:读取所有数据
            QByteArray data = reply->readAll();
            qDebug() << "下载内容大小:" << data.size() << "bytes";
            // qDebug() << "内容预览:" << data.left(100); 
        }

        // 6. 清理垃圾! (非常重要)
        // reply 是 new 出来的,需要释放。
        // 使用 deleteLater() 比 delete 更安全,因为它会等待当前事件循环结束。
        reply->deleteLater(); 
        
        // 如果是控制台程序,可以在这里退出
        // QCoreApplication::quit();
    }

private:
    QNetworkAccessManager *manager;
};

// 配合 main.cpp 使用时,只需实例化 Downloader 并调用 doDownload()

⚠️ 核心注意事项 (避坑指南)

  1. 异步即正义:

    Qt 的网络模块是异步的(Asynchronous)。当你调用 manager->get() 时,函数会立刻返回,界面不会卡死。数据还没回来呢!你必须依赖 finished 信号或者 readyRead 信号来知道数据什么时候到了。

    不要试图写 while(!reply->isFinished()) 这样的死循环去等数据,那会让你的 GUI 失去响应!

  2. 对象的所属权:

    QNetworkReply 对象是由 QNetworkAccessManager 创建并返回给你的,它的生杀大权在你手里。Qt 不会自动帮你 delete 它。一定要在用完后调用 reply->deleteLater(),否则随着请求增多,内存会爆涨。

  3. HTTPS 支持:

    如果你访问 https:// 开头的网址,Qt 需要 OpenSSL 库的支持。如果在 Windows 上运行报错(TLS/SSL 错误),通常是因为缺少 libssl 和 libcrypto 相关的 DLL 文件。

  4. 大文件下载:

    上面的例子用了 readAll(),这适合小文件(文本、图片)。如果要下载几百兆的大文件,不要在 finished 里一次性读完(内存会爆)。

    正确做法:连接 QNetworkReply::readyRead 信号,每收到一点数据就写入硬盘文件,然后清空内存缓冲区。

/******************************************************************************************************/

常用函数:

为了让你快速上手,我们把这三个类中最常用的"高频函数"挑出来。记住我们的比喻:调度员 (Manager)运单 (Request)包裹/回执 (Reply)

以下是按使用频率排序的核心函数清单:

1. QNetworkAccessManager (调度员)

核心任务:发起请求。你最常调用的就是那几个动词。

函数名 作用 (通俗解释) 典型场景
get(const QNetworkRequest &request) 发起下载。告诉调度员:"去把这个东西取回来"。 下载网页 HTML、图片、JSON 数据。
post(const QNetworkRequest &request, const QByteArray &data) 发起上传。告诉调度员:"把这些数据送到服务器去"。 登录表单提交、上传文件、发送 JSON 指令。
finished(QNetworkReply *reply) (信号) 任务结束大喇叭。无论成功失败,只要这趟活干完了,就发信号。 在这里统一处理结果、断开连接。

2. QNetworkRequest (运单)

核心任务:配置请求的细节。在交给调度员之前,你得把单子填好。

函数名 作用 (通俗解释) 典型代码
setUrl(const QUrl &url) 写地址。这是必填项,没有地址没法送。 req.setUrl(QUrl("http://..."));
setRawHeader(const QByteArray &name, const QByteArray &value) 贴自定义标签。最通用的设置头信息的方法。 设置 User-Agent (伪装浏览器)、Token (身份验证)。 req.setRawHeader("User-Agent", "Chrome");
setHeader(KnownHeaders header, const QVariant &value) 贴标准标签。Qt 预定义了一些常用头,用枚举值设置,防止你拼写错误。 req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

3. QNetworkReply (包裹/回执)

核心任务:处理结果。这是功能最丰富的一个类,因为它既包含数据,也包含状态。

A. 获取数据 (拆包裹)

由于它继承自 QIODevice,文件操作函数它都能用:

  • readAll()一锅端。一次性读取所有下载的数据。适合小文件(文本、API 响应)。

  • read(qint64 maxSize)一点点读。配合流式写入文件时使用。

B. 检查状态 (看回执)
  • error()有没有出错? 返回错误枚举值(如 NoError, ConnectionRefusedError)。

  • errorString()错误详情。返回人类可读的错误描述字符串。

  • attribute(QNetworkRequest::Attribute code)查底单 。最常用的是获取 HTTP 状态码

    • 常用写法reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); (用来判断是 200 还是 404)。
C. 监控进度 (物流追踪)
  • downloadProgress(qint64 bytesReceived, qint64 bytesTotal)(信号) 下载进度更新。常用来更新进度条(UI)。

  • uploadProgress(qint64 bytesSent, qint64 bytesTotal)(信号) 上传进度更新。

D. 生命周期管理 (扔包装)
  • deleteLater()销毁对象 。千万别直接 delete reply,因为可能信号还没发完。用这个函数告诉 Qt:"等当前没事了,把它清理掉"。

  • abort()拒收/取消。强行中断当前的下载或上传。

⚡️ 常用"连招" (代码片段)

场景 1:最常用的 Header 设置 (JSON 通信)

cpp 复制代码
QNetworkRequest request(QUrl("http://api.example.com/data"));
// 告诉服务器我要发JSON过去
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
// 告诉服务器我希望收JSON回来
request.setRawHeader("Accept", "application/json");

场景 2:检查 HTTP 状态码 (防坑)

很多时候网络是通的 (NoError),但服务器报错了 (比如 500 服务器错误)。所以既要看 error() 也要看状态码。

cpp 复制代码
// 在 finished 槽函数中
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid() && statusCode.toInt() == 200) {
    // 真正成功
}

场景 3:实现进度条

cpp 复制代码
// 假设你有一个 QProgressBar *bar
connect(reply, &QNetworkReply::downloadProgress, this, [=](qint64 now, qint64 total){
    if (total > 0) {
        bar->setMaximum(total);
        bar->setValue(now);
    }
});

gpt:

当然可以!我来给你一个生动形象、详细的介绍 ,让你理解 Qt 网络模块中 HTTP/FTP 下载相关的三个核心类:QNetworkAccessManagerQNetworkRequestQNetworkReply,并附上简单的使用示例。


1️⃣ 概念与类介绍

Qt 的网络模块可以让我们像"点对点邮递员"一样访问互联网资源。核心的"邮递链条"分为三环:

1. QNetworkAccessManager --- 网络经理

  • 比喻:像一个万能的快递公司,你告诉它"我要寄这个包裹(请求)",它负责把包裹送到目的地并把回信带回来。

  • 功能

    • 发送各种网络请求(HTTP GET、POST、FTP 等)。

    • 管理请求和应答。

    • 处理异步操作(Qt 网络默认是异步的)。

  • 注意 :通常整个应用程序中可以共用一个 QNetworkAccessManager 实例,因为它内部会管理连接池和缓存。


2. QNetworkRequest --- 网络请求

  • 比喻:你写给快递公司的"快递单",里面有:

    • 地址(URL)

    • 包裹类型(请求头,比如 Content-Type)

    • 其他信息(Cookie、User-Agent 等)

  • 功能

    • 封装请求目标(URL)。

    • 设置请求头(如请求格式、认证信息)。

  • 常用方法

    • setUrl(const QUrl &url):设置请求地址。

    • setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value):设置 HTTP 请求头。


3. QNetworkReply --- 网络回复

  • 比喻:快递公司给你送回来的"回信",里面有:

    • 返回数据(文件内容、网页源码)

    • 状态码(200 OK、404 Not Found)

    • 错误信息(如果请求失败)

  • 功能

    • 异步返回服务器响应。

    • 可以通过信号接收数据,如:

      • readyRead():有数据可读。

      • finished():请求完成。

      • errorOccurred():请求出错。

  • 注意 :请求完成后,需要手动删除 QNetworkReply(或者使用 deleteLater())。


2️⃣ 网络下载示例

下面我们用 Qt 下载一个网页内容作为演示(异步操作):

cpp 复制代码
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 1. 创建网络经理(全局可用)
    QNetworkAccessManager manager;

    // 2. 创建请求(写快递单)
    QUrl url("https://www.example.com");
    QNetworkRequest request(url);

    // 3. 发送 GET 请求
    QNetworkReply *reply = manager.get(request);

    // 4. 连接信号槽处理异步下载
    QObject::connect(reply, &QNetworkReply::finished, [&]() {
        if (reply->error() == QNetworkReply::NoError) {
            QByteArray data = reply->readAll();  // 获取返回内容
            qDebug() << "下载完成,内容长度:" << data.size();
            qDebug() << data.left(200); // 打印前200字节
        } else {
            qDebug() << "下载出错:" << reply->errorString();
        }
        reply->deleteLater(); // 清理
        QCoreApplication::quit();
    });

    return a.exec();
}

🔹 运行原理

  1. manager.get(request) 发起 GET 请求。

  2. Qt 内部使用事件循环异步处理网络请求。

  3. 请求完成后,finished() 信号触发。

  4. 通过 reply->readAll() 获取数据。

  5. 最后调用 deleteLater() 清理对象。


3️⃣ 小结

类名 功能 比喻
QNetworkAccessManager 发送请求,管理网络事务 快递公司
QNetworkRequest 封装请求信息 快递单
QNetworkReply 接收服务器响应 回信

使用步骤

  1. 创建 QNetworkAccessManager

  2. 创建并配置 QNetworkRequest(URL + 请求头)。

  3. 调用 manager.get(request)post() 发起请求。

  4. 通过 QNetworkReply 信号处理返回数据。

  5. 完成后清理 QNetworkReply


/***********************************************************************************************************/

常用函数:

明白了,你想了解 Qt 网络模块中 常用的函数 ,尤其是用于 上传和下载文件或数据 的。我们可以针对 QNetworkAccessManagerQNetworkRequestQNetworkReply 来整理一张"速查表",并举例说明。


1️⃣ QNetworkAccessManager --- 网络操作核心

功能 常用函数 说明
发起 GET 请求 QNetworkReply *get(const QNetworkRequest &request) 下载数据或文件
发起 POST 请求 QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data) 上传数据,如表单或 JSON
发起 PUT 请求 QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data) 上传完整文件或内容
发起 DELETE 请求 QNetworkReply *deleteResource(const QNetworkRequest &request) 删除服务器资源
发起自定义请求 QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data = QByteArray()) 支持任意 HTTP 方法

注意 :返回的都是 QNetworkReply*,操作是异步的,需要通过信号处理。


2️⃣ QNetworkRequest --- 请求配置

功能 常用函数 说明
设置请求 URL void setUrl(const QUrl &url) 必须指定下载或上传目标地址
设置请求头 void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) ContentTypeHeader, ContentLengthHeader
设置自定义原始头 void setRawHeader(const QByteArray &headerName, const QByteArray &value) 例如自定义 AuthorizationUser-Agent
设置超时 / 缓存策略 void setAttribute(QNetworkRequest::Attribute code, const QVariant &value) CacheLoadControlAttribute

3️⃣ QNetworkReply --- 响应处理

功能 常用函数/信号 说明
读取数据 QByteArray readAll() 获取服务器返回的数据
监听数据可读 readyRead() 信号 数据可以分段读取
请求完成 finished() 信号 数据下载完成
获取 HTTP 状态码 int attribute(QNetworkRequest::HttpStatusCodeAttribute) 如 200、404
获取错误 QNetworkReply::NetworkError error() 判断请求是否成功
上传进度 uploadProgress(qint64 bytesSent, qint64 bytesTotal) 信号 POST/PUT 上传进度
下载进度 downloadProgress(qint64 bytesReceived, qint64 bytesTotal) 信号 GET 下载进度

4️⃣ 下载文件示例(带进度)

cpp 复制代码
QNetworkAccessManager manager;
QUrl url("https://www.example.com/file.zip");
QNetworkRequest request(url);

QNetworkReply *reply = manager.get(request);

// 下载进度
QObject::connect(reply, &QNetworkReply::downloadProgress, [](qint64 bytesReceived, qint64 bytesTotal){
    qDebug() << "下载进度:" << bytesReceived << "/" << bytesTotal;
});

QObject::connect(reply, &QNetworkReply::finished, [reply](){
    if(reply->error() == QNetworkReply::NoError){
        QFile file("file.zip");
        if(file.open(QIODevice::WriteOnly))
            file.write(reply->readAll());
        file.close();
        qDebug() << "下载完成";
    } else {
        qDebug() << "下载失败:" << reply->errorString();
    }
    reply->deleteLater();
});

5️⃣ 上传文件示例(POST)

cpp 复制代码
QNetworkAccessManager manager;
QUrl url("https://www.example.com/upload");
QNetworkRequest request(url);

// 设置 Content-Type
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");

// 读取本地文件
QFile file("file_to_upload.bin");
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
file.close();

// 上传
QNetworkReply *reply = manager.post(request, data);

// 上传进度
QObject::connect(reply, &QNetworkReply::uploadProgress, [](qint64 bytesSent, qint64 bytesTotal){
    qDebug() << "上传进度:" << bytesSent << "/" << bytesTotal;
});

// 上传完成
QObject::connect(reply, &QNetworkReply::finished, [reply](){
    if(reply->error() == QNetworkReply::NoError)
        qDebug() << "上传完成";
    else
        qDebug() << "上传失败:" << reply->errorString();
    reply->deleteLater();
});

✅ 小结

  • 下载文件QNetworkAccessManager::get() + QNetworkReply::downloadProgress()

  • 上传文件QNetworkAccessManager::post()/put() + QNetworkReply::uploadProgress()

  • 请求头设置QNetworkRequest::setHeader() / setRawHeader()

  • 读取数据QNetworkReply::readAll() 或分段读取

  • 状态与错误检查QNetworkReply::error() + finished()

项目教程代码:

cpp 复制代码
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   12_imagedownload
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @net           www.openedv.com
* @date          2021-04-16
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QFile>
#include <QLabel>
#include <QPushButton>
#include <QProgressBar>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 网络管理 */
    QNetworkAccessManager *networkAccessManager;

    /* 标签 */
    QLabel *label[3];

    /* 按钮 */
    QPushButton *pushButton;

    /* 下载进度条 */
    QProgressBar *progressBar;

    /* 水平布局 */
    QHBoxLayout *hBoxLayout[2];

    /* 垂直布局 */
    QVBoxLayout *vBoxLayout;

    /* 水平容器 */
    QWidget *hWidget[2];

    /* 垂直容器 */
    QWidget *vWidget;

    /* 链接输入框 */
    QLineEdit *lineEdit;

private slots:
    /* 读取数据 */
    void readyReadData();

    /* 读取数据 */
    void readyReadData(QNetworkReply*);

    /* 响应完成处理 */
    void replyFinished();

    /* 下载进度管理 */
    void imageDownloadProgress(qint64, qint64);

    /* 点击开始下载 */
    void startDownload();

    /* 响应错误处理函数 */
    void networkReplyError(QNetworkReply::NetworkError);
};
#endif // MAINWINDOW_H
cpp 复制代码
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   12_imagedownload
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @net           www.openedv.com
* @date          2021-04-16
*******************************************************************/
#include "mainwindow.h"
#include <QMessageBox>
#include <QCoreApplication>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 标签0, 显示下载的图像 */
    label[0] = new QLabel();
    /* 标签1, 显示URL标签 */
    label[1] = new QLabel();
    /* 下载进度标签 */
    label[2] = new QLabel();

    /* 下载图片链接输入框 */
    lineEdit = new QLineEdit();

    /* 下载按钮 */
    pushButton = new QPushButton();

    /* 下载进度条 */
    progressBar = new QProgressBar();

    /* 水平布局 */
    hBoxLayout[0] = new QHBoxLayout();
    hBoxLayout[1] = new QHBoxLayout();

    /* 垂直布局 */
    vBoxLayout = new QVBoxLayout();

    /* 水平容器 */
    hWidget[0] = new QWidget();
    hWidget[1] = new QWidget();

    /* 垂直容器 */
    vWidget = new QWidget();

    label[1]->setText("URL链接:");
    label[2]->setText("文件下载进度:");

    pushButton->setText("下载");

    /* 设置下载链接地址,在浏览器右键复制图片下载地址即可 */
    lineEdit->setText("https://img1.baidu.com/it/u=3908406816,2850982836&"
                      "fm=253&fmt=auto&app=120&f=JPEG?w=889&h=500");
    /* 设置标签的最小显示大小 */
    label[0]->setMinimumSize(this->width(),
                             this->height() * 0.75);

    /* 根据文本文字大小自适应大小 */
    label[1]->setSizePolicy(QSizePolicy::Fixed,
                            QSizePolicy::Fixed);
    label[2]->setSizePolicy(QSizePolicy::Fixed,
                            QSizePolicy::Fixed);
    pushButton->setSizePolicy(QSizePolicy::Fixed,
                              QSizePolicy::Fixed);

    /* 水平布局0添加元素 */
    hBoxLayout[0]->addWidget(label[1]);
    hBoxLayout[0]->addWidget(lineEdit);
    hBoxLayout[0]->addWidget(pushButton);

    /* 设置水平布局0为水平容器的布局0 */
    hWidget[0]->setLayout(hBoxLayout[0]);

    /* 水平布局1添加元素 */
    hBoxLayout[1]->addWidget(label[2]);
    hBoxLayout[1]->addWidget(progressBar);

    /* 设置水平布局1为水平容器的布局1 */
    hWidget[1]->setLayout(hBoxLayout[1]);

    /* 垂直布局添加元素 */
    vBoxLayout->addWidget(label[0]);
    vBoxLayout->addWidget(hWidget[0]);
    vBoxLayout->addWidget(hWidget[1]);

    /* 设置垂直布局为垂直容器的布局 */
    vWidget->setLayout(vBoxLayout);

    /* 设置居中 */
    setCentralWidget(vWidget);

    /* 网络管理 */
    networkAccessManager = new QNetworkAccessManager(this);

    /* 信号槽连接 */
    connect(pushButton, SIGNAL(clicked()),
            this, SLOT(startDownload()));

}

MainWindow::~MainWindow()
{
}

void MainWindow::startDownload()
{
    /* 获取URL链接 */
    QUrl newUrl(QUrl(lineEdit->text()));

    /* 如果下载链接无效,则直接返回 */
    if (!newUrl.isValid()) {
        QMessageBox::information(this, "error", "invalid url");
        return;
    }

    /* 网络请求 */
    QNetworkRequest  networkRequest;

    /* 设置下载的地址 */
    networkRequest.setUrl(newUrl);

    /* 网络响应 */
    QNetworkReply *newReply =
            networkAccessManager->get(networkRequest);

    /* 信号槽连接 */
    connect(newReply, SIGNAL(finished()),
            this, SLOT(replyFinished()));
    /* 请求成功后,立刻去读,才能成功下载图片 */
    // connect(newReply, SIGNAL(readyRead()),
    //      this, SLOT(readyReadData()));
    connect(newReply, SIGNAL(downloadProgress(qint64, qint64)),
            this, SLOT(imageDownloadProgress(qint64, qint64)));
    connect(newReply,
            SIGNAL(error(QNetworkReply::NetworkError)),
            this,
            SLOT(networkReplyError(QNetworkReply::NetworkError )));
}

void MainWindow::readyReadData()
{
    /* 设置按钮不可用,防止未完成,再次点击 */
    pushButton->setEnabled(false);

    /* 获取信号发送者 */
    QNetworkReply *reply = (QNetworkReply *)sender();

    QFile imageFile;
    /* 保存到当前路径,名称为"下载的.jpg" */
    imageFile.setFileName(QCoreApplication::applicationDirPath()
                          + "/下载的.jpg");

    /* 如果此图片已经存在,则删除 */
    if (imageFile.exists())
        imageFile.remove();

    /* 读取数据 */
    QByteArray data =  reply->readAll();
    /* 如果数据为空,返回 */
    if (data.isEmpty()) {
        qDebug()<<"data is null, please try it again!"<<endl;
        return;
    }

    /* 判断是不是JPG格式的图片,如果不是则返回,如果不需要判断JPG可以删除这部分代码 */
    if (! (data[0] == (char)0xff
           && data[1] == (char)0xd8
           && data[data.size() - 2] == (char)0xff
           && data[data.size() - 1] == (char)0xd9)) {
        qDebug()<<"not JPG data, please try it again!"<<endl;
        return;
    }

    /* 转为QPixmap */
    QPixmap pixmap;
    pixmap.loadFromData(data);
    pixmap.save(imageFile.fileName());
}

void MainWindow::readyReadData(QNetworkReply *reply)
{
    /* 设置按钮不可用,防止未完成,再次点击 */
    pushButton->setEnabled(false);

    QFile imageFile;
    /* 保存到当前路径,名称为"下载的.jpg" */
    imageFile.setFileName(QCoreApplication::applicationDirPath()
                          + "/下载的.jpg");

    /* 如果此图片已经存在,则删除 */
    if (imageFile.exists())
        imageFile.remove();

    /* 读取数据 */
    QByteArray data =  reply->readAll();
    /* 如果数据为空,返回 */
    if (data.isEmpty()) {
        qDebug()<<"data is null, please try it again!"<<endl;
        return;
    }

    /* 判断是不是JPG格式的图片,如果不是则返回 */
    if (! (data[0] == (char)0xff
           && data[1] == (char)0xd8
           && data[data.size() - 2] == (char)0xff
           && data[data.size() - 1] == (char)0xd9)) {
        qDebug()<<"not JPG data, please try it again!"<<endl;
        return;
    }

    /* 转为QPixmap */
    QPixmap pixmap;
    pixmap.loadFromData(data);
    pixmap.save(imageFile.fileName());
}

void MainWindow::replyFinished()
{
    /* 获取信号发送者 */
    QNetworkReply *reply = (QNetworkReply *)sender();

    /* 防止内存泄漏 */
    reply->deleteLater();

    /* 立刻读取数据 */
    readyReadData(reply);

    /* 判断当前执行程序下的图像是否下载完成 */
    QFile imageFile(QCoreApplication::applicationDirPath()
                    + "/下载的.jpg");
    if (imageFile.exists()) {
        /* 显示下载的图像 */
        label[0]->setPixmap(QPixmap(imageFile.fileName()));
        qDebug() <<"已经成功下载,文件路径为:"
                <<imageFile.fileName()<<endl;
    } else
        /* 清空显示 */
        label[0]->clear();

    /* 设置按钮可用 */
    pushButton->setEnabled(true);
}

void MainWindow::imageDownloadProgress(qint64 bytes,
                                       qint64 totalBytes)
{
    /* 设置进度条的最大值 */
    progressBar->setMaximum(totalBytes);
    /* 设置当前值 */
    progressBar->setValue(bytes);
}

/* 网络响应处理函数 */
void MainWindow::networkReplyError(QNetworkReply::NetworkError
                                   error)
{
    switch (error) {
    case QNetworkReply::ConnectionRefusedError:
        qDebug()<<"远程服务器拒绝连接"<<endl;
        break;
    case QNetworkReply::HostNotFoundError:
        qDebug()<<"找不到远程主机名"<<endl;
        break;
    case QNetworkReply::TimeoutError:
        qDebug()<<"与远程服务器连接超时"<<endl;
        break;
    default:
        break;
    }
}
相关推荐
YY&DS1 小时前
Qt 快速搭建局域网 HTTP 下载服务(兼容 IE/Chrome/Edge/Firefox)
chrome·qt·http
xuchaoxin13751 小时前
cdn节点代理的副作用@fail2ban对接cdn封锁恶意请求ip@fail2ban封锁ip有效性问题
运维·网络·cdn·cloudflare
西幻凌云1 小时前
了解计算机网络的“物理根基”——物理层与数据链路层
网络·网络协议·计算机网络·数据链路层·物理层
q***69772 小时前
使用 Qt 插件和 SQLCipher 实现 SQLite 数据库加密与解密
数据库·qt·sqlite
极地星光2 小时前
Qt/C++ 单例模式深度解析:饿汉式与懒汉式实战指南
c++·qt·单例模式
白狐_7984 小时前
网络基础核心问题深度解析:从IP/MAC到IPv6与路由配置
网络·tcp/ip·macos
板鸭〈小号〉4 小时前
应用层协议 HTTP
网络·网络协议·http
拾忆,想起5 小时前
Dubbo服务超时与重试策略配置指南:构建 resilient 微服务架构
服务器·网络·微服务·云原生·架构·dubbo
MarkHD5 小时前
车辆TBOX科普 第28次 AT命令集与移动通信技术入门:从基础到4G/5G网络详解
网络·5g