Portál AbcLinuxu, 30. dubna 2025 21:24

Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)

1. 9. 2009 | David Watzke
Články - Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)  

V dnešním díle se budu věnovat modulu QtNetwork, konkrétně si ukážeme nějaké základy práce s protokoly HTTP a FTP.

Obsah

Úvod do síťování v Qt

link

Práci s protokoly HTTP a FTP implementují třídy QHttpQFtp, které jsou v Qt dostupné už mnoho let. Ve verzi 4.4 se objevila třída QNetworkAccessManager, která poskytuje jednodušší, ale přesto mocnější API a vývojáři doporučují její použití v nových programech.

Síťování je asynchronní a využívá technologie signálů a slotů. Pošlete požadavek, vytvoří se objekt s odpovědí a tento objekt potom vyšle signál, když je požadavek vyřízený nebo když dojde k chybě.

Při vytváření projektů pracujících s modulem QtNetwork v Qt Creatoru si nezapomeňte modul vyžádat. Pokud projekt vytváříte ručně, tak si do .pro souboru přidejte řádek QT += network.

HTTP hlavičky

link

Ukážeme si, jak poslat HTTP požadavek GET a vypsat získané hlavičky. Program funguje takto:

$ ./httphead http://www.abclinuxu.cz/
Expires: Fri, 22 Dec 2000 05:00:00 GMT
Set-Cookie: JSESSIONID=5117noj9m1ik;Path=/
Content-Type: text/html; charset=utf-8
Last-Modified: Thu, 27 Aug 2009 13:38:35 GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Content-Encoding: gzip
Server: Jetty(6.1.11)

http.h: API.

#ifndef HTTP_H
#define HTTP_H

#include <QObject>
#include <QNetworkReply>
#include <QTextStream>

class QNetworkAccessManager;

class HTTP : public QObject
{
	Q_OBJECT
public:
	HTTP();
	int run();

private:
	QNetworkAccessManager *manager;
	// objekt pro zápis na std. výstup
	QTextStream cout;

private slots:
	void gotReply(QNetworkReply* reply);
	void gotError(QNetworkReply::NetworkError);
};

#endif // HTTP_H

http.cpp

#include "http.h"

/*
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QStringList>
*/

// includne celý modul QtNetwork, který s sebou vtáhne QtCore
#include <QtNetwork>
// tohle potřebujeme pro stdout
#include <cstdio>

HTTP::HTTP() : cout(stdout, QIODevice::WriteOnly)
{
	manager = new QNetworkAccessManager(this);

	// spojíme signál značící vyřízení požadavku se slotem,
	// který zpracuje odpověď
	connect(manager, SIGNAL(finished(QNetworkReply*)),
		this, SLOT(gotReply(QNetworkReply*)));
}

int HTTP::run()
{
	// zkontrolujeme počet argumentů, případně vypíšeme nápovědu
	QStringList args = qApp->arguments();
	if(args.size() != 2)
	{
		cout << "usage: " << args.at(0) << " [http url]\n";
		cout.flush();
		// odsud nelze ukončit program běžným způsobem,
		// vysvětlení najdete u souboru main.cpp
		return 1;
	}

	QString url = args.at(1);
	// pošleme HTTP požadavek HEAD na dané URL, uložíme si ukazatel na odpověď
	QNetworkReply* reply = manager->head(QNetworkRequest(QUrl(url)));
	// spojíme odpověď se slotem, který zpracuje případnou chybu
	connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
		this, SLOT(gotError(QNetworkReply::NetworkError)));

	return 0;
}

// vypíše všechny hlavičky a ukončí program
void HTTP::gotReply(QNetworkReply* reply)
{
	foreach(QByteArray header, reply->rawHeaderList())
	{
		QByteArray value = reply->rawHeader(header);
		cout << header << ": " << value << "\n";
	}
	cout.flush();
	qApp->exit(0);
}

// vypíše chybu a ukončí program
void HTTP::gotError(QNetworkReply::NetworkError e)
{
	switch(e)
	{
	case QNetworkReply::NoError:
		break;
	case QNetworkReply::ConnectionRefusedError:
		cout << tr("connection refused") << "\n";
		break;
	case QNetworkReply::RemoteHostClosedError:
		cout << tr("the remote server closed the connection") << "\n";
		break;
	case QNetworkReply::HostNotFoundError:
		cout << tr("host not found") << "\n";
		break;
	case QNetworkReply::TimeoutError:
		cout << tr("connection timed out") << "\n";
		break;)
	case QNetworkReply::ProtocolUnknownError:
		cout << tr("unknown protocol (did you forget http:// ?)") << "\n";
		break;
	default:
		cout << tr("an error occured") << "\n";
		break;
	}
	cout.flush();
	qApp->exit(2);
}

main.cpp: V metodě run() nelze ukončit program klasickým způsobem, protože ještě neběží smyčka událostí. Jelikož zde program ukončit potřebujeme (pokud uživatel nezadá argument – URL), tak jsem zvolil jedno z mnoha řešení, jak to obejít.

#include <QCoreApplication>

#include "http.h"

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

	HTTP main;
	if(main.run() != 0)
		return 1;

	return a.exec();
}

Zdrojáky si můžete stáhnout v archívu httphead.tar.bz2.

Stahování souborů

link

Jelikož má QNetworkAccessManager takové pěkné rozhraní pro stahování souborů, rozhodl jsem se předvést zde metodu get() právě pro stahování z FTP.

Výstup programu vypadá následovně:

$ ./ftpget ftp://gentoo.mirror.web4u.cz/releases/amd64/autobuilds/20090716/install-amd64-minimal-20090716.iso
downloaded 121188352 bytes (out of 121188352), which is 100%
download finished, file saved as install-amd64-minimal-20090716.iso

$ ./ftpget http://gentoo.mirror.web4u.cz/snapshots/portage-20090818.tar.bz2
downloaded 36430733 bytes (out of 36430733), which is 100%
download finished, file saved as portage-20090818.tar.bz2

ftpget.h: API.

#ifndef FTPGET_H
#define FTPGET_H

#include <QObject>
#include <QTextStream>
#include <QNetworkReply>

class QFile;
class QNetworkAccessManager;

class FtpGet : public QObject
{
	Q_OBJECT
public:
	FtpGet();
	int run();

private:
	QFile* file;
	QString fileName;
	QNetworkAccessManager* manager;
	QNetworkReply* reply;
	QTextStream cout;

private slots:
	void downloadFinished(QNetworkReply*);
	void showProgress(qint64, qint64);
	void writeData();
	void handleError(QNetworkReply::NetworkError);
};

#endif // FTPGET_H

ftpget.cpp:

#include "ftpget.h"

#include <QtNetwork>

FtpGet::FtpGet() : cout(stdout, QIODevice::WriteOnly)
{
	manager = new QNetworkAccessManager(this);

	connect(manager, SIGNAL(finished(QNetworkReply*)),
		this, SLOT(downloadFinished(QNetworkReply*)));
}

int FtpGet::run()
{
	// zkontrolujeme počet argumentů, případně vypíšeme nápovědu
	QStringList args = qApp->arguments();
	if(args.size() != 2)
	{
		cout << "usage: " << args.at(0) << " [ftp://file]\n";
		cout.flush();
		return 1;
	}

	QUrl url(args.at(1));

	// získáme z URL název souboru
	fileName = url.path().split('/').last();
	if(fileName.isEmpty())
		fileName = QString("file_from_%1").arg(url.host());

	// otevřeme soubor pro zápis
	file = new QFile(fileName);
	if(!file->open(QIODevice::WriteOnly))
	{
		cout << "could not open the file (" << fileName << ")\n";
		cout.flush();
		return 1;
	}

	// začneme stahovat
	reply = manager->get(QNetworkRequest(url));

	// propojíme signály s funkcemi pro zápis, výpis průběhu a zpracování chyb
	connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
		this, SLOT(showProgress(qint64,qint64)));
	connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
		this, SLOT(handleError(QNetworkReply::NetworkError)));
	connect(reply, SIGNAL(readyRead()), this, SLOT(writeData()));

	return 0;
}

// zapíše průběžně stažená data
void FtpGet::writeData()
{
	file->write(reply->readAll());
}

// zavře soubor, informuje o dokončení stahování a ukončí program
void FtpGet::downloadFinished(QNetworkReply* reply)
{
	Q_UNUSED( reply )

	if(file && file->isOpen())
		file->close();
	cout << QString("\ndownload finished, file saved as %1\n").arg(fileName);
	cout.flush();
	qApp->quit();
}

// vypíše průběh stahování
void FtpGet::showProgress(qint64 dl, qint64 all)
{
	// procenta
	int perc = 0;

	// spočítá kolik procent je staženo (pokud to lze)
	if(dl > 0 && all > 0)
		perc = dl / (all / 100);

	// pomocný řetězec s celkovou velikostí
	QString sizestr = (all == -1) ? "[unknown total size]" : QString::number(all);

	// pomocný řetězec s procenty
	QString percstr = "";
	if (perc != 0)
		percstr = QString(", which is %1%").arg(perc);

	// vypíše info o průběhu stahování
	cout << QString("\rdownloaded %1 bytes (out of %2)").arg(dl).arg(sizestr) << percstr << "\t\t";
}

// zpracuje chybu (v tomto případě jen vypíše chybový kód, viz první program pro úplné řešení)
void FtpGet::handleError(QNetworkReply::NetworkError e)
{
	cout << "\nan error ocurred (code #" << e << "), see http://qt.nokia.com/doc/qnetworkreply.html#NetworkError-enum\n";
	qApp->exit(2);
}

main.cpp:

#include <QCoreApplication>

#include "ftpget.h"

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

	FtpGet main;
	if(main.run() != 0)
		return 1;

	return a.exec();
}

Zdrojáky si můžete stáhnout v archívu ftpget.tar.bz2.

Seriál Qt 4 – Konzolové programy (dílů: 4)

První díl: Konzolové programy v Qt 4 – 1 (úvod), poslední díl: Konzolové programy v Qt 4 – 4 (UDP server a klient).
Předchozí díl: Konzolové programy v Qt 4 – 1 (úvod)
Následující díl: Konzolové programy v Qt 4 – 3 (TCP server)

Související články

Seriál: Qt 4 - psaní grafických programů
Cmake: zjednoduš si život
Seriál: Kommander
Seriál: KDE: tipy a triky
Seriál: Začíname KProgramovať

Odkazy a zdroje

doc.qtsoftware.com

Další články z této rubriky

LLVM a Clang – více než dobrá náhrada za GCC
Ze 4 s na 0,9 s – programovací jazyk Vala v praxi
Reverzujeme ovladače pro USB HID zařízení
Linux: systémové volání splice()
Programování v jazyce Vala - základní prvky jazyka

Diskuse k tomuto článku

1.9.2009 00:25 sharpiq
Rozbalit Rozbalit vše Re: Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)
Odpovědět | Sbalit | Link | Blokovat | Admin

Zdravim,

mozno mi niekto bude vediet poradit - ked som posledne robil s metodami ktore pracuju s HTTP/HTTPS napriek tomu ze Qt network je deklarovane ako asynchronne na Windows strojoch to sposobovalo "zamrznutie" UI aplikacie pri poziadavkach na server. Ked som to prehnal cez gdb tak Qt vytvoralo cca 5-6 threadov na kazde volanie. Nevie niekto cim to moze byt sposobovane? Vyskytovalo sa to len na Windows, Qt bolo verzie 4.2.2.

David Watzke avatar 1.9.2009 00:44 David Watzke | skóre: 74 | blog: Blog... | Praha
Rozbalit Rozbalit vše Re: Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)

Zkus aktuální verzi Qt (dnes 4.5.2) a případně nahlaš chybu vývojářům (viz web qt.nokia.com). Půjde o windows-specific bug.

Pokud to zatím potřebuješ obejít, tak síťování dělej v odděleném vlákně QThread.

“Being honest may not get you a lot of friends but it’ll always get you the right ones” ―John Lennon
1.9.2009 10:40 Ladicek | skóre: 28 | blog: variace | Havlíčkův brod
Rozbalit Rozbalit vše Re: Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)
Odpovědět | Sbalit | Link | Blokovat | Admin
Má nějaký smysl, že signál finished spojuješ se slotem gotReply/downloadFinished už v konstruktoru, zatímco všechny ostatní až v metodě run (a to dokonce teprve poté, co spustíš samotné stahování)? Intuitivně bych to všechno dělal na jednom místě (asi v run před zahájením stahování)…
Ještě na tom nejsem tak špatně, abych četl Viewegha.
David Watzke avatar 1.9.2009 11:17 David Watzke | skóre: 74 | blog: Blog... | Praha
Rozbalit Rozbalit vše Re: Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)
Jo, má to smysl. V konstruktoru dělám spojení s tím managerem, zatímco v tý metodě spojuju sloty se signály socketu - a ten socket získám teprve po spuštění metody (head) manageru, takže to jinak ani nejde.
“Being honest may not get you a lot of friends but it’ll always get you the right ones” ―John Lennon
1.9.2009 11:50 Ladicek | skóre: 28 | blog: variace | Havlíčkův brod
Rozbalit Rozbalit vše Re: Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)
Ha, toho jsem si nevšiml, jak jsem to tak rychle prolítnul. Díky.
Ještě na tom nejsem tak špatně, abych četl Viewegha.
1.9.2009 10:54 Jirka
Rozbalit Rozbalit vše void QTcpServer::incomingConnection ( int socketDescriptor )
Odpovědět | Sbalit | Link | Blokovat | Admin

Jak korektne implementovat void QTcpServer::incomingConnection ( int socketDescriptor ) pro potomka tridy QTcpServer? Prijde mi, ze to neni mozne. Ta metoda je virtualni, aby mohla byt prepsana. Ale jeji implementace v QTcpServer vyuziva privatni funkce, takze kdyz ji chce nekdo implementovat znovu, nemuze podle me zajistit stejnou funkcnost.

David Watzke avatar 1.9.2009 11:18 David Watzke | skóre: 74 | blog: Blog... | Praha
Rozbalit Rozbalit vše Re: void QTcpServer::incomingConnection ( int socketDescriptor )
Pokud chceš stejnou funkčnost, tak ji prostě nech jak je. Nemusíš ji reimplementovat za každou cenu. Pokud chceš aby dělala to, co dělá + něco navíc, tak si vytvoř vlastní a volej v ní
QTcpServer::incomingConnection(socketDescriptor);
“Being honest may not get you a lot of friends but it’ll always get you the right ones” ―John Lennon
29.6.2023 09:55 Adilkhatri
Rozbalit Rozbalit vše Re: Konzolové programy v Qt 4 – 2 (práce s HTTP a FTP)
Odpovědět | Sbalit | Link | Blokovat | Admin
A to a great degree brilliant blog passage. We are really grateful for your blog passage. fight, law usage You will find an extensive measure of techniques in the wake of heading off to your post. I was absolutely examining for. An obligation of appreciation is all together for such post and please keep it up. Mind blowing work. 먹튀검증

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.