Evgenii Legotckoi
13 декабря 2015 г. 21:58

Qt/C++ - Урок 035. Скачивание файла по HTTP с помощью QNetworkAccessManager

Для работы с сетью кроме использования классов QTcpSocket или QUdpSocket можно использовать QNetworkAccessManager. Данный класс предоставляет функционал для отправки запросов по сети и получения ответов и удобен для работы с протоколом HTTP.

Поэтому предлагаю написать приложение, которое позволит скачать xml-файл с сайта и записать его файл на локальном диске компьютера.

Логика приложения следующая:

  1. Скачать файл;
  2. Записать его на локальный диск по следующему пути C:/example/file.xml;
  3. Прочитать записанный файл и отобразить данные в QTextEdit.

Структура проекта для работы с HTTP

Структура проекта следующая:

  • DownloadHttp.pro - профайл проекта;
  • main.cpp - основной файл исходных кодов приложения;
  • widget.h - заголовочный файл окна приложения;
  • widget.cpp - файл исходных кодов окна приложения;
  • downloader.h - заголовочный файл класса для скачивания файла;
  • doqnloader.cpp - файл исходных кодов класса для скачивания файла.

DownloadHttp.pro и main.cpp

main.cpp создаётся по умолчанию и не модифицируется, тогда как в файле DownloadHttp.pro необходимо подключить модуль network.

  1. #-------------------------------------------------
  2. #
  3. # Project created by QtCreator 2015-12-13T21:02:32
  4. #
  5. #-------------------------------------------------
  6.  
  7. QT += core gui network
  8.  
  9. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  10.  
  11. TARGET = DownloadHttp
  12. TEMPLATE = app
  13.  
  14.  
  15. SOURCES += main.cpp\
  16. widget.cpp \
  17. downloader.cpp
  18.  
  19. HEADERS += widget.h \
  20. downloader.h
  21.  
  22. FORMS += widget.ui

widget.ui

В окне приложения располагается кнопка, по нажатию которой будет производиться запуск скачивания данных с сайта и QTextEdit, в который будут помещены данные из сохранённого файла.

widget.h

Заголовочный файл окна приложения. В нём мы подключим заголовочный файл класса Downloader, который будет отвечать за скачивание данных с сайта и сохранения их в файл. Естественно объявим объект данного класса Downloader. Также присутствует объявление сигнатуры слота для чтения данных из сохранённого файла по окончанию скачивания.

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3.  
  4. #include <QWidget>
  5. #include <QFile>
  6.  
  7. #include <downloader.h>
  8.  
  9. namespace Ui {
  10. class Widget;
  11. }
  12.  
  13. class Widget : public QWidget
  14. {
  15. Q_OBJECT
  16.  
  17. public:
  18. explicit Widget(QWidget *parent = 0);
  19. ~Widget();
  20.  
  21. private slots:
  22. void readFile();
  23.  
  24. private:
  25. Ui::Widget *ui;
  26. Downloader *downloader; // Объявляем объект класса для скачивания данных по http
  27. };
  28.  
  29. #endif // WIDGET_H

widget.cpp

В исходных кодах присутствует два коннекта сигналов к слотам. Один коннект отвечает за обработку нажатия кнопки, а второй за чтение данных из файла по окончанию скачивания файла.

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3.  
  4. Widget::Widget(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::Widget)
  7. {
  8. ui->setupUi(this);
  9.  
  10. downloader = new Downloader(); // Инициализируем Downloader
  11.  
  12. // по нажатию кнопки запускаем получение данных по http
  13. connect(ui->pushButton, &QPushButton::clicked, downloader, &Downloader::getData);
  14. // по окончанию получения данных считываем данные из файл
  15. connect(downloader, &Downloader::onReady, this, &Widget::readFile);
  16.  
  17. }
  18.  
  19. Widget::~Widget()
  20. {
  21. delete ui;
  22. }
  23.  
  24. void Widget::readFile()
  25. {
  26. QFile file("C:/example/file.xml");
  27. if (!file.open(QIODevice::ReadOnly)) // Открваем файл, если это возможно
  28. return; // если открытие файла невозможно, выходим из слота
  29. // в противном случае считываем данные и устанавилваем их в textEdit
  30. ui->textEdit->setText(file.readAll());
  31. }

donwloader.h

А теперь заголовочный файл самого виновника торжества. В нём Мы объявляем экземпляр класса QNetworkAccesManager, а также методы для инициализации запроса к сайт по его URL и обработки полученного ответа.

  1. #ifndef DOWNLOADER_H
  2. #define DOWNLOADER_H
  3.  
  4. #include <QObject>
  5. #include <QNetworkAccessManager>
  6. #include <QNetworkRequest>
  7. #include <QNetworkReply>
  8. #include <QFile>
  9. #include <QUrl>
  10. #include <QDebug>
  11.  
  12. class Downloader : public QObject
  13. {
  14. Q_OBJECT
  15. public:
  16. explicit Downloader(QObject *parent = 0);
  17.  
  18. signals:
  19. void onReady();
  20.  
  21. public slots:
  22. void getData(); // Метод инициализации запроса на получение данных
  23. void onResult(QNetworkReply *reply); // Слот обработки ответа о полученных данных
  24.  
  25. private:
  26. QNetworkAccessManager *manager; // менеджер сетевого доступа
  27. };
  28.  
  29. #endif // DOWNLOADER_H

downloader.cpp

ВНИМАНИЕ!!! - Проверьте доступность URL из примера перед запуском приложения, чтобы не биться головой об стену, если сайт просто не доступен. А лучше замените на тот URL, до которого Вы желаете достучаться. А также возможно имеет смысл поменять расширение файла на txt.

  1. #include "downloader.h"
  2.  
  3. Downloader::Downloader(QObject *parent) : QObject(parent)
  4. {
  5. // Инициализируем менеджер ...
  6. manager = new QNetworkAccessManager();
  7. // ... и подключаем сигнал о завершении получения данных к обработчику полученного ответа
  8. connect(manager, &QNetworkAccessManager::finished, this, &Downloader::onResult);
  9. }
  10.  
  11. void Downloader::getData()
  12. {
  13. QUrl url("http://www.mtbank.by/currxml.php"); // URL, к которому будем получать данные
  14. QNetworkRequest request; // Отправляемый запрос
  15. request.setUrl(url); // Устанавлвиваем URL в запрос
  16. manager->get(request); // Выполняем запрос
  17. }
  18.  
  19. void Downloader::onResult(QNetworkReply *reply)
  20. {
  21. // Если в процесе получения данных произошла ошибка
  22. if(reply->error()){
  23. // Сообщаем об этом и показываем информацию об ошибках
  24. qDebug() << "ERROR";
  25. qDebug() << reply->errorString();
  26. } else {
  27. // В противном случае создаём объект для работы с файлом
  28. QFile *file = new QFile("C:/example/file.xml");
  29. // Создаём файл или открываем его на перезапись ...
  30. if(file->open(QFile::WriteOnly)){
  31. file->write(reply->readAll()); // ... и записываем всю информацию со страницы в файл
  32. file->close(); // закрываем файл
  33. qDebug() << "Downloading is completed";
  34. emit onReady(); // Посылаем сигнал о завершении получения файла
  35. }
  36. }
  37. }

Итог

В результате вы получите приложение, которое сдёрнет данные со странички сайта и отобразит из в QTextEdit, как показано на ниже следующем рисунке. Демонстрация работы приложения доступна в видеоуроке.

Ссылка на скачивание проекта в zip-архиве: downloadhttp.zip

Видеоурок

Вам это нравится? Поделитесь в социальных сетях!

C
  • 21 апреля 2017 г. 23:18

Здравствуйте! Когда тестировал ваш проект в режиме Debug выводит такую ошибку:

-1: error: Exception at 0x74f3b782, code: 0xd: , flags=0x1 (execution cannot be continued) (first chance)
после закрытия программы. Также и с моим проектом. Я искал ошибку и нашел ошибку вот в этой строке: manager = new QNetworkAccessManager(); Также и у меня когда создаю обект класса QNetworkAccessManager. В чем пожет быть проблема? Спасибо.
Evgenii Legotckoi
  • 21 апреля 2017 г. 23:40

Здравствуйте! Не знаю. На всякий случай я скачал проект и проверил сейчас у себя его работу, но никаких исключений не выкидывает. Проверял на Qt 5.8 GCC 64 bit под Ubuntu 16.04. Проект работает стабильно.

Какой версией Qt пользуетесь?

C
  • 22 апреля 2017 г. 0:08

Я использую Qt 5.7.1 с Microsoft Visual Studio 2015 (Windows 10) и на Qt 5.9.0 Beta 2 также. У меня мой и ваш проект работает стабильно, но если через Debug тестировать и закрыть программу то такая ошибка появляется. Пробовал через Visual Studio 2015 отлаживать, но выводит ошибку:

Exception thrown at 0x74CCB782 (KernelBase.dll) in MyTestApp.exe: 0x0000071A: The remote procedure call was canceled, or if a call time-out was specified, the call timed out. If there is a handler for this exception, the program may be safely continued.
Или иногда эту ошибку:
Exception thrown at 0x74CCB782 (KernelBase.dll) in MyTestApp.exe: 0x0000000D: The data is invalid.
После выводит код ассемблера:
74CCB782 mov ecx,dword ptr [esp+54h]
К примеру на Windows 8.1 x64 тестировал то такую ошибку выводит как предупреждение: -1: error: Exception at 0x74f3b782, code: 0xd: , flags=0x1 (execution cannot be continued) (first chance) А на Windows 10 как ошибку. Не могу точно найти где ошибка, но если закоментировать код инициализации QNetworkAccessManager: manager = new QNetworkAccessManager(); тогда ошибки нет и соотвественно не работает подключение сети в программе.
Evgenii Legotckoi
  • 22 апреля 2017 г. 0:33

Не похоже, чтобы это была проблема с Qt или ошибкой в коде. У меня такой проблемы как у вас не возникает. Проверил на следующем:

  1. Ubuntu 16.04 - GCC 64 bit - Qt5.8
  2. Windows 7 - MinGIW 32 bit - Qt5.7
  3. Windows 7 - MSVC 2015 - Qt5.7

Исключения по окончании работы не выбрасываются. У вас фигурирует в исключениях KernelBase.dll . Скорее всего это или придурь компилятора или сама библиотека кривая.

Но для очистки совести могу посоветовать передать указатель на parent объект при создании объекта QNetworkAccessManager

manager = new QNetworkAccessManager(this);
C
  • 22 апреля 2017 г. 0:57

Да, и если указать parent (manager = new QNetworkAccessManager(this);) также ошибка. Я вот что еще заметил когда тестировал на Visual Studio 2015, Thread: [1328]wlanapi.dll!_NotificationApcThreadProc@4, то есть ошибка/сбой программы в этой функции, но у меня нет подключения к wlanapi (библиотеки WiFi) в программе, значит ошибка происходит только на Windows 10 и с библиотекой Qt (Qt5Networkd.dll/Qt5Network.dll).

Evgenii Legotckoi
  • 22 апреля 2017 г. 10:32

Считаю, что библиотека Qt Network работает нормально. Дело в том, что данная библиотека перебирает все возможные варианты подключений, в том числе и wi-fi подключения, когда пытается найти выход в интернет. Мне думается, что тут исключительно локальная проблема с системными библиотеками на Windows 10.

Zeland
  • 1 марта 2019 г. 3:12

Евгений, спасибо за полезную статью. Немного отредактировал добавленные к уроку файлы, чтобы запустить программу на версии фреймворма Qt 5.10.
DownloadHttp.zip DownloadHttp.zip

Evgenii Legotckoi
  • 1 марта 2019 г. 16:46

Вам спасибо за дополнение.

F
  • 19 сентября 2019 г. 15:45

А вот как выгрузить файл на сервер по http протоколу? Допустим на regRu. И как получить путь файла, которой отображается в файловом менеджере regRu, чтобы загрузить его.

Evgenii Legotckoi
  • 25 сентября 2019 г. 13:53

Не реализовывал такое, так что пока не подскажу. Если будет такой опыт, то напишу статью.

ИП
  • 26 сентября 2019 г. 15:25

Как произвести загрузку используя HTTPS протокол?

F
  • 26 сентября 2019 г. 19:28
  • (ред.)

точно так же как и http. Просто url адрес будет с https.

Андрей madmentat
  • 26 сентября 2022 г. 21:23
  • (ред.)

Здравствуйте! Подскажите, пожалуйста, как сделать так, чтобы программа срабатыала без нажатия кнопки? Ну чисто при загрузке формы... Я так понимаю, надо что-то поменять в этой строчке

  1. connect(ui->pushButton, &QPushButton::clicked, downloader, &Downloader::getData);
  2.  

Но вот что именно? Если не сложно, свяжитесь со мной. А то я спать спокойно не смогу пока не выясню как это делается.

Evgenii Legotckoi
  • 27 сентября 2022 г. 18:41

Попробуйте просто вызвать метод getData в конструкторе класса

g
  • 12 ноября 2023 г. 21:35

Добрый день.
Изучаю Qt на ваших уроках. Всё нормально работает на Linux. А под Win один раз запустилось, а сейчас вместо данных сайта получается ошибк "Unable to write". Куда копать, ума не приложу.
Кто подскажет, буду благдарен.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь