Qt提供了QFtp和QHttp类两个类与FTP和HTTP协议配合使用。这些协议让文件的下载和上传变得更加容易。另外http协议也使向网站服务器发送请求并重新找回结果的过程变得简单易行。Qt还提供了较低级的QTcpSocket和QUdpSocket类,他们实现TCP和UDP传输协议。两个协议都用于创建网络客户端和服务器应用程序。若要创建服务器应用程序,还需要QTcpServer类来处理引入的TCP连接。还可以使用QSslSocket代替QTcpSocket来建立安全的SSL/TLS连接。
FTP客户端
QFtp类在Qt中实现了FTP协议的客户端程序,它提供了非常多的函数来执行多数常见的FTP操作,同时还可以执行任意的FTP指令。
QFtp类是异步工作的,若调用一个像get()或put()这样的函数,它会立即返回并且仅在控制权回到Qt的事件循环时才发生数据传输。这样就保证了在执行FTP指令时,用户界面可以保持响应。
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = QCoreApplication::arguments();
if (args.count() != 2) {
std::cerr << "Usage: ftpget url" << std::endl
<< "Example:" << std::endl
<< " ftpget ftp://ftp.trolltech.com/mirrors"
<< std::endl;
return 1;
}
FtpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}创建一个QCoreApplication, 而不是他的子类QApplication,以避免连接到QtGui库。QCoreApplication::arguments()函数返回命令行参数作为一个QString列表,采用第一项作为应用程序的名称,同时删除-style等Qt指定的参数。main函数的核心是FtpGet对象和getFile()调用。如果该函数调用成功,则让事件循环一直运行下去,直到下载完毕。
class FtpGet : public QObject
{
Q_OBJECT
public:
FtpGet(QObject *parent = 0);
bool getFile(const QUrl &url);
signals:
void done();
private slots:
void ftpDone(bool error);
private:
QFtp ftp;
QFile file;
};FtpGet类的声明,该类有一个公有函数getFile(), 可用来获取URL指定的文件。QUrl类提供了一个高级接口,用来提取URL的不同部分,如文件名称、路径、协议和接口等。类中还有一个私有槽ftpDone(),当文件传输完成时调用;当文件下载完成时,发送一个done()信号;还有两个私有变量:ftp变量和file变量。
FtpGet::FtpGet(QObject *parent)
: QObject(parent)
{
connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
}在构造函数中,将QFtp::done(bool)信号和ftpDone(bool)私有槽连接起来,当所有的请求都已处理完时,QFtp就发生done(bool)信号,bool参数指明是否有错误发生。
bool FtpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
std::cerr << "Error: Invalid URL" << std::endl;
return false;
}
if (url.scheme() != "ftp") {
std::cerr << "Error: URL must start with 'ftp:'" << std::endl;
return false;
}
if (url.path().isEmpty()) {
std::cerr << "Error: URL has no path" << std::endl;
return false;
}
QString localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "ftpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
std::cerr << "Error: Cannot write file "
<< qPrintable(file.fileName()) << ": "
<< qPrintable(file.errorString()) << std::endl;
return false;
}
ftp.connectToHost(url.host(), url.port(21));
ftp.login();
ftp.get(url.path(), &file);
ftp.close();
return true;
}
void FtpGet::ftpDone(bool error)
{
if (error) {
std::cerr << "Error: " << qPrintable(ftp.errorString())
<< std::endl;
} else {
std::cerr << "File downloaded as "
<< qPrintable(file.fileName()) << std::endl;
}
file.close();
emit done();
}
//这些FTP指令一旦得以执行,就可以关闭相应的文件并且发送我们自己的done()信号。QFtp提供了一些FTP指令,包括connectToHost(), login(), close(), list(), cd(), get(), put(), remove(), mkdir(), rename()。所有这些函数都可以调度一个FTP指令,并且可以返回一个标识这个指令的ID号。对于传输模式(默认为被动模式)和传输类型(默认为二进制类型)的控制,也是可能的。
使用rawCommand()可以执行任意的FTP指令,如执行一个SITE CHMOD指令: ftp.rawCommand("SITE CHMOD 755 fortune");
当QFtp开始执行一个指令的时候,它发射一个commandStarted(int)信号,而当这个指令完成的时候,它发射commandFinished(int, bool)信号。int指令是识别一个指令的ID号。
HTTP客户端
QHttp类在Qt中实现了HTTP协议的客户端程序,它提供了非常多的函数来执行多数常见的FTP操作,同时还可以执行任意的FTP指令。
QHttp类是异步工作的,若调用一个像get()或post()这样的函数,它会立即返回并且仅在控制权回到Qt的事件循环时才发生数据传输。这样就保证了在执行HTTP指令时,用户界面可以保持响应。
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = QCoreApplication::arguments();
if (args.count() != 2) {
std::cerr << "Usage: httpget url" << std::endl
<< "Example:" << std::endl
<< " httpget http://doc.trolltech.com/index.html"
<< std::endl;
return 1;
}
HttpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}
class HttpGet : public QObject
{
Q_OBJECT
public:
HttpGet(QObject *parent = 0);
bool getFile(const QUrl &url);
signals:
void done();
private slots:
void httpDone(bool error);
private:
QHttp http;
QFile file;
};
HttpGet::HttpGet(QObject *parent)
: QObject(parent)
{
connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool)));
}
bool HttpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
std::cerr << "Error: Invalid URL" << std::endl;
return false;
}
if (url.scheme() != "http") {
std::cerr << "Error: URL must start with 'http:'" << std::endl;
return false;
}
if (url.path().isEmpty()) {
std::cerr << "Error: URL has no path" << std::endl;
return false;
}
QString localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "httpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
std::cerr << "Error: Cannot write file "
<< qPrintable(file.fileName()) << ": "
<< qPrintable(file.errorString()) << std::endl;
return false;
}
http.setHost(url.host(), url.port(80));
http.get(url.path(), &file);
http.close();
return true;
}
void HttpGet::httpDone(bool error)
{
if (error) {
std::cerr << "Error: " << qPrintable(http.errorString())
<< std::endl;
} else {
std::cerr << "File downloaded as "
<< qPrintable(file.fileName()) << std::endl;
}
file.close();
emit done();
}QHttp类提供了多种操作,包括setHost(), get(), post(), head(). 如果站点需要认证,则setUser()可以用来提供用户名和口令。QHttp可以用程序员自编的套接字设置而不用内部自带的QTcpSocket。这就使利用可靠的QtSslSocket来往SSL(加密套接字协议层)或者TLS(加密传输协议层)上实现HTTP成为可能。
http.setHost("www.example.com");
http.post("/cgi/somescript.py", "x=2000&y=320", &file);
Qt中的socket编程
在LINUX下进行网络编程,我们可以使用LINUX提供的统一的套接字接口。但是这种方法牵涉到太多的结构体,比如IP地址,端口转换等,不熟练的人往往容易犯这样那样的错误。QT中提供的SOCKET完全使用了类的封装机制,使用户不需要接触底层的各种结构体操作。而且它采用QT本身的signal-slot机制,使编写的程序更容易理解。
QT中共提供四个与套按字相关的类,分别是:
QServerSocket:TCP-based server
QSocket: Buffered TCP connection
QSocketDevice: Platform-independent low-level socket API
QSocketNotifier: Support for socket callbacks
下面介绍使用QT进行网络编程,我们使用一个简单的C/S模式网络程序说明如何使用QT中的套接字。同时我们用TCP和UDP两种协议实现这个程序(该程序客户端与服务端各向对方发送一个字符口串“abc”)
1、UDP实现
UDP是不连接协议,没有客户端与服务端的概念。
1)建立套接字相关对象
QSocketDevice *MUReceiveSocket; //套接字对象
QSocketNotifier *MSocketNotifier; //套接字监听对象
2)初始化套接字相关对象
MUReceiveSocket=new QSocketDevice(QSocketDevice::Datagram);
//UDP初始化
QHostAddress MyAddress;
QString FakeAddress;
FakeAddress = get_eth1_ip(); //取得接口IP
MyAddress.setAddress(FakeAddress);
MUReceiveSocket->bind(MyAddress,Port);
//绑定到指定网络接口地址(IP),指定逻辑端口
MSocketNotifier = new QSocketNotifier(MUReceiveSocket->socket(),QSocketNotifier::Read,0,"MSocketNotifier");
//监听MUReceiveSocket套接字
3)定义用实现响应slot
virtual void OnMReceive();
void Client::OnMReceive()
{
int ByteCount,ReadCount;
char *IncommingChar;
fprintf(stderr,"Load a piece of Message!\n");
ByteCount=MUReceiveSocket->bytesAvailable();
IncommingChar=(char *)malloc(ByteCount+1);
ReadCount=MUReceiveSocket->readBlock(IncommingChar,ByteCount);
IncommingChar[ByteCount]='\0';
fprintf(stderr,“%s“,IncommingChar); //打印接收的字符串
}
4)关联套接字的signal和接收slot
connect(MSocketNotifier,SIGNAL(activated(int)),this,SLOT(OnMReceive()));
//当MSocketNotifier检测到MUReceiveSocket活跃时调用OnMReceive
5)发送字符串
char information[20];
strcpy(information,“abc“);
MUReceiveSocket->writeBlock(information,length,MyAddress,2201);
2、TCP实现
TCP的实现与UDP的实现大同小异,它是面象连接的协议。这里只介绍与UDP不同的地方。
服务端:
1)套接字对象的定义
比UDP多定义一个套接字,一个用来监听端口,一个用来通信。
建立一个QSSocket类继承QServerSocket
QSSocket *ServerSocket; //TCP-based server
QSocketDevice *ClientSocket;
QSocketNotifier *ClientNotifier;
QSocketNotifier *ServerNotifier;
2)套接字的初始化
QHostAddress MyAddress;
QString FakeAddress;
FakeAddress = "127.0.0.1";
MyAddress.setAddress(FakeAddress);
UINT Port=1234;
ServerSocket=new QSSocket(MyAddress,Port,this,0); //指定监听地址及端口
//这里也可以使用QServerSocket类
ClientSocket=new QSocketDevice(QSocketDevice::Stream);
ClienttNotifier = new QSocketNotifier(ClientSocket->socket(),QSocketNotifier::Read,0,"ClientSocket");
3)响应连接
只需要在QSSocket的构造函数里添加如下代码:
ServerSocket->newConncetion(ClientSocket->socket());
当收到客户端的连接后,ClientSocket自动响应,并接收数据。
4)接收信息slot与UDP一致,这里不在叙述。
客户端实现:
客户端的实现与UDP实现大同小异,不同的地方只是客户端套接字不需要bind端口,因为连接上服务端后TCP会保持这个连接,直到通信的结束。