|
Posted on 2008-04-26 09:24 HYin 阅读(6034) 评论(2) 编辑 收藏 引用
我们网络实验使用socket实现一个ftp客户端,前几天我一个很困惑一些技术上的问题,因为在网上基本上没有这样的样例,所以我将代码贴了上来,不是完整的代码,只有一个核心部分,我想要实现的就是像QFtp一样的功能,不过还有很多地方要完善。
 /**//*
* HLftp.h
* \date 2008-04-22
* \author HanYin
*/

#ifndef HL_FTP_H
#define HL_FTP_H

#include "HLutil.h"

#include <QtNetwork>
#include <QString>

class QUrlInfo;

namespace hy
  {
class HLftp : public QObject
 {
Q_OBJECT
public :
explicit HLftp( QObject * parent =NULL );
~HLftp();

 enum State {
Unconnected,
HostLookup,
Connecting,
Connected,
LoggingIn,
LoggedIn,
Closing,
Closed
};

 enum Command {
None,
SetTransferMode,
SetProxy,
ConnectToHost,
Login,
Close,
List,
Cd,
Get,
Put,

Remove, // haven't accomplished
Mkdir,
Rmdir,
Rename,
RawCommand
};

 enum TransferType {
Binary,
Ascii
};

 enum TransferMode {
Active,
Passive
};

public :
 /**//**
* Connects to an FTP server and logs in with the supplied username and* password.
*/
int connectToHost( const QString & hostname, uint port = 21 );

 /**//**
* logs in with the supplied username and* password.
*/
int login( const QString & username = QString("anonymous"), const QString & password = QString("anonymous") );

 /**//**
* Disconnects from the FTP server.
*/
int disconnect();

 /**//**
* Get the directory dir of the FTP server it is connected to.
* Default to working directory.
*/
int list( const QString &dir = QString() );

 /**//**
* Changes the working directory (like cd). Returns true if successful.
*/
int cwd( const QString & dir );

 /**//**
* Enter binary mode for sending binary files.
*/
int bin();

 /**//**
* Enter ASCII mode for sending text files. This is usually the default mode.
* Make sure you use binary mode if you are sending images or other binary
* data, as ASCII mode is likely to corrupt them.
*/
int ascii();

 /**//**
* Sends a file to be stored on the FTP server.
* The file is sent in passive mode to avoid NAT or
* firewall problems at the client end.
*/
int put( QIODevice *dev, const QString &file );

 /**//**
* download a file from the FTP server.
* The file is sent in passive mode to avoid NAT or
* firewall problems at the client end.
*/
int get( const QString &file, QIODevice *dev );

 /**//**
* Get information of Server system.
*/
int syst();

 /**//**
* Cut down current data link.
*/
int abort();

 /**//**
* Set transfer mode.
*/
int setTransferMode( TransferMode mode );

 /**//**
* get current state.
*/
State currentState() const;

 /**//**
* get current command.
*/
Command currentCommand() const;

 /**//**
* get current transfer type.
*/
TransferType currentTransferType() const;

 /**//**
* get current transfer type.
*/
void openDebug( bool b )
 { m_bDebug = b; }

signals :
void commandStarted( int id );
void commandFinished( int id, bool error );
void listInfo(const QUrlInfo & url);

void readyRead();

void dataTransferProgress(qint64, qint64); // haven't accomplished.

protected :
 /**//**
* Returns the working directory of the FTP server it is connected to.
*/
QString pwd();

 /**//**
* Send a raw command to the FTP server.
*/
void sendLine( QString cmd );

 /**//**
* Get a raw command to the FTP server.
*/
QString getLine();

private :
QTcpSocket * m_pControlSocket;
QTcpSocket * m_pDataSocket;

State m_eCurrentState;
Command m_eCurrentCommand;
TransferType m_eTransferType;
TransferMode m_eTransferMode;
ServerSystemType m_eServerSystemType;

QString m_sDataIP;
uint m_iDataPort;
bool m_bSetModeSuccess;

// all ids for states.
int connect_id;
int login_id;
int close_id;
int list_id;
int pwd_id;
int cwd_id;
int bin_id;
int ascii_id;
int put_id;
int get_id;
int transfertype_id;
int syst_id;
int abort_id;

// For Debug.
bool m_bDebug;

};

inline HLftp::State HLftp::currentState() const
 {
return m_eCurrentState;
}

inline HLftp::Command HLftp::currentCommand() const
 {
return m_eCurrentCommand;
}

inline HLftp::TransferType HLftp::currentTransferType() const
 {
return m_eTransferType;
}
}

#endif
 /**//*
* HLftp.cpp
* \date 2008-04-22
* \author HanYin
*/

#include "HLftp.h"

#include <QDataStream>
#include <QUrlInfo>

namespace hy
  {
HLftp::HLftp( QObject * parent )
: m_pDataSocket(NULL), m_pControlSocket(NULL)
 {
m_bDebug = true;
m_eCurrentState = Unconnected;
m_eCurrentCommand = None;
m_eTransferType = Ascii;
m_eTransferMode = Active;
m_eServerSystemType = SYS_OTHERS;
m_pControlSocket = new QTcpSocket();

// temporarily for this.
connect_id = 100;
login_id = 200;
close_id = 300;
}

HLftp::~HLftp()
 {
if ( NULL != m_pControlSocket )
delete m_pControlSocket;
if ( NULL != m_pDataSocket )
delete m_pDataSocket;
}

int HLftp::connectToHost( const QString & hostname, uint port )
 {
// if the socket is busy, close it.
if ( m_pControlSocket->isOpen() )
m_pControlSocket->close();

// connect to the host
m_eCurrentState = Connecting;
m_eCurrentCommand = ConnectToHost;
emit commandStarted(connect_id);

m_pControlSocket->connectToHost( hostname, port );
QString response = getLine();
if ( ! response.startsWith("220") )
 {
emit commandFinished( connect_id, true ); // connect failure
return connect_id;
}
else
 {
emit commandFinished( connect_id, false ); // connect successfully
}

// connect to the host
m_eCurrentState = Connected;
return connect_id;
}

int HLftp::login( const QString & username, const QString & password )
 {
// set current state to be logging in.
m_eCurrentState = LoggingIn;
m_eCurrentCommand = Login;
emit commandStarted(login_id);


// send user name and password if needed
sendLine( QObject::tr("USER ") + username );
QString response = getLine();
if ( !response.startsWith("331") && !response.startsWith("230") )
 {
emit commandFinished( login_id, true );
return login_id;
}

// if needing password
if ( response.startsWith("331") )
 {
sendLine( QObject::tr("PASS ") + password );
response = getLine();
if ( ! response.startsWith("230") )
 {
emit commandFinished( login_id, true );
return login_id;
}
else
 {
emit commandFinished( login_id, false );
}
}
else
 {
emit commandFinished( login_id, false );
}

// set current state to be connected.
m_eCurrentState = LoggedIn;
return login_id;
}

int HLftp::disconnect()
 {
// set current state to be closing.
m_eCurrentState = Closing;
m_eCurrentCommand = Close;
emit commandStarted(close_id);

// Aborts the current command and deletes all scheduled commands.
m_pControlSocket->abort();

// Closes the connection to the FTP server.
m_pControlSocket->close();

emit commandFinished( close_id, false );

// set current state to be closing.
m_eCurrentState = Closed;
return close_id;
}

int HLftp::list( const QString &dir )
 {
m_eCurrentCommand = List;
// Get directory to be listed.
QString directory = dir;
if ( directory.isEmpty() ) // if empty, current working directory.
 {
directory = pwd();
}

// sending command, make it passive.
sendLine("PASV");

QString response = getLine();
if ( ! response.startsWith("227") )
 {
emit commandFinished( get_id, true );
return list_id;
}
else
 {
m_eTransferMode = Passive;
}
int begin = response.indexOf('(') +1;
int end = response.indexOf( ')', begin );
QString substring = response.mid( begin, end-begin ); // cut out ip and data port.

int i;
QString temp_ip;
temp_ip.clear();
// get IP string.
end =-1;
for ( i=0; i<3; i++ )
 {
begin = end +1;
end = substring.indexOf( ',', begin+1 );
temp_ip.append( substring.mid( begin, end-begin ) );
temp_ip.append(".");
}

begin = end +1;
end = substring.indexOf( ',', begin+1 );
temp_ip.append( substring.mid( begin, end-begin ) );

// get port.
uint temp_port =0;
begin = end +1;
end = substring.indexOf( ',', begin );
temp_port = substring.mid( begin, end-begin ).toUInt() * 256;

begin = end +1;
temp_port += substring.mid( begin, -1 ).toUInt();

// Create a new data link from Server to get all lists.
emit commandStarted(list_id);
sendLine( QObject::tr("LIST ") + directory );

m_pDataSocket = new QTcpSocket(this);
m_pDataSocket->connectToHost( temp_ip, temp_port );
m_pDataSocket->write( "USER\r\n", 8 );

response = getLine();
if (! response.startsWith("150") )
 {
emit commandFinished( put_id, true );
return list_id;
}

char buffer[500];
int byteRead = 0;
 while (true) {
if ( ! m_pDataSocket->waitForReadyRead() )
break;

byteRead = m_pDataSocket->readLine( buffer, 500 );
QString temp_string(buffer);
QUrlInfo url = HLutil::parseFileList( temp_string, m_eServerSystemType );
emit listInfo( url );
}

m_pDataSocket->flush();
m_pDataSocket->close();
delete m_pDataSocket;

response = getLine();
if ( ! response.startsWith("226 ") )
 {
emit commandFinished( get_id, true );
}
else
 {
emit commandFinished( get_id, false );
}

return list_id;
}

int HLftp::cwd( const QString & dir )
 {
m_eCurrentCommand = Cd;
emit commandStarted(cwd_id);
// sending command.
sendLine( QObject::tr("CWD ") + dir );

QString response = getLine();
if ( ! response.startsWith("250") )
 {
emit commandFinished( cwd_id, true );
return cwd_id;
}
else
 {
emit commandFinished( cwd_id, false );
return cwd_id;
}
}

int HLftp::bin()
 {
emit commandStarted(bin_id);
// sending command.
sendLine( QObject::tr("TYPE I") );

QString response = getLine();
if ( ! response.startsWith("200") )
 {
m_eTransferType = Binary;
emit commandFinished( bin_id, true );
return bin_id;
}
else
 {
emit commandFinished( bin_id, false );
return bin_id;
}
}

int HLftp::ascii()
 {
emit commandStarted(ascii_id);
// sending command.
sendLine( QObject::tr("TYPE A") );

QString response = getLine();
if ( ! response.startsWith("200") )
 {
m_eTransferType = Ascii;
emit commandFinished( ascii_id, true );
return ascii_id;
}
else
 {
emit commandFinished( ascii_id, false );
return ascii_id;
}
}

int HLftp::put(QIODevice *dev, const QString &file )
 {
m_eCurrentCommand = Put;
// sending command, make it passive.
sendLine("PASV");

QString response = getLine();
if ( ! response.startsWith("227") )
 {
emit commandFinished( put_id, true );
return put_id;
}
else
 {
m_eTransferMode = Passive;
}
int begin = response.indexOf('(')+1;
int end = response.indexOf(')', begin);
QString substring = response.mid( begin, end-begin ); // cut out ip and data port.

int i;
QString temp_ip;
temp_ip.clear();
// get IP string.
for ( i=0; i<4; i++ )
 {
begin = end +1;
end = substring.indexOf( ',', begin );
temp_ip.append( substring.mid( begin, end-begin ) );
}

// get port.
uint temp_port =0;
begin = end +1;
end = substring.indexOf( ',', begin );
temp_port = substring.mid( begin, end-begin ).toUInt() * 256;

begin = end +2;
temp_port += substring.mid( begin, -1 ).toUInt();

// now set up a new data link use given ip and port.
emit commandStarted(put_id);
sendLine( QObject::tr("STOR ") + file );

m_pDataSocket = new QTcpSocket();
m_pDataSocket->connectToHost( temp_ip, temp_port );

response = getLine();
if (! response.startsWith("150 ") )
 {
emit commandFinished( put_id, true );
return put_id;
}

char buffer[4096];
int byteRead = 0;
memset( buffer, 0, 4096 );
 while ( ( byteRead = dev->read( buffer, 4096 ) ) != -1 ) {
m_pDataSocket->write( buffer, byteRead );
}

m_pDataSocket->flush();
m_pDataSocket->close();
dev->close();
delete m_pDataSocket;

response = getLine();
if ( ! response.startsWith("226 ") )
 {
emit commandFinished( put_id, true );
}
else
 {
emit commandFinished( put_id, false );
}

return put_id;

}

int HLftp::get( const QString &file, QIODevice * dev )
 {
m_eCurrentCommand = Get;

// sending command, make it passive.
sendLine("PASV");

QString response = getLine();
if ( ! response.startsWith("227") )
 {
emit commandFinished( get_id, true );
return list_id;
}
else
 {
m_eTransferMode = Passive;
}
int begin = response.indexOf('(') +1;
int end = response.indexOf( ')', begin );
QString substring = response.mid( begin, end-begin ); // cut out ip and data port.

int i;
QString temp_ip;
temp_ip.clear();
// get IP string.
end =-1;
for ( i=0; i<3; i++ )
 {
begin = end +1;
end = substring.indexOf( ',', begin+1 );
temp_ip.append( substring.mid( begin, end-begin ) );
temp_ip.append(".");
}

begin = end +1;
end = substring.indexOf( ',', begin+1 );
temp_ip.append( substring.mid( begin, end-begin ) );

// get port.
uint temp_port =0;
begin = end +1;
end = substring.indexOf( ',', begin );
temp_port = substring.mid( begin, end-begin ).toUInt() * 256;

begin = end +1;
temp_port += substring.mid( begin, -1 ).toUInt();

// now set up a new data link use given ip and port.
emit commandStarted(get_id);
sendLine( QObject::tr("RETR ") + file );

m_pDataSocket = new QTcpSocket();
m_pDataSocket->connectToHost( temp_ip, temp_port );

response = getLine();
if (! response.startsWith("150 ") )
 {
emit commandFinished( put_id, true );
return put_id;
}

char buffer[4096];
int byteRead = 0;
memset( buffer, 0, 4096 );
 while ( m_pDataSocket->waitForReadyRead() ) {
if ( ( byteRead = m_pDataSocket->read( buffer, 4096 ) ) == -1 )
 {
break;
}
dev->write( buffer, byteRead );
}

m_pDataSocket->flush();
m_pDataSocket->close();
dev->close();
delete m_pDataSocket;
m_pDataSocket = NULL;

response = getLine();
if ( ! response.startsWith("226 ") )
 {
emit commandFinished( get_id, true );
}
else
 {
emit commandFinished( get_id, false );
}

return get_id;
}

int HLftp::syst()
 {
emit commandStarted(syst_id);
// sending command.
sendLine( QObject::tr("SYST") );

QString response = getLine();
if ( ! response.startsWith("200") )
 {
emit commandFinished( syst_id, true );
return syst_id;
}
else
 {
emit commandFinished( syst_id, false );
return syst_id;
}
}

int HLftp::abort()
 {
if ( NULL != m_pDataSocket )
m_pDataSocket->abort();

return abort_id;
}

QString HLftp::pwd()
 {
emit commandStarted(pwd_id);
// send command.
sendLine( QObject::tr("PWD") );

QString response = getLine();
if ( response.startsWith("257") )
 {
int opening = response.indexOf('/');
int closing = response.indexOf('\"', opening + 1);

emit commandFinished( pwd_id, false );
QString temp = response.mid( opening, closing-opening );
return temp;
}
else
 {
emit commandFinished( pwd_id, true );
return QString();
}
}

QString HLftp::getLine()
 {
if ( ! m_pControlSocket->isOpen() )
 {
qDebug() << "error : HLftp::getLine() : socket is not open\n" << endl;
return QString();
}

char buffer[500];
m_pControlSocket->waitForReadyRead();
m_pControlSocket->readLine( buffer, 500 );
m_pControlSocket->read(m_pControlSocket->bytesAvailable());

qDebug() << "> get: " << buffer << endl;

return QString(buffer);
}

void HLftp::sendLine( QString cmd )
 {
if ( ! m_pControlSocket->isOpen() )
 {
qDebug() << "error : HLftp::sendLine( QString cmd ) : socket is not open\n" << endl;
}

m_pControlSocket->waitForBytesWritten();
cmd.append("\r\n");
m_pControlSocket->write( cmd.toStdString().c_str(), cmd.size() );
qDebug() << "> send: " << cmd.toStdString().c_str() << endl;

}

}

下面是一个辅助类,主要是提供一些算法
 /**//*
* HLutil.h
* \date 2008-04-22
* \author HanYin
*/

#ifndef HL_UTIL_H
#define HL_UTIL_H

class QString;
class QUrlInfo;
class QStringList;

namespace hy
  {
typedef unsigned int UINT;

// Temporarily put here, to be upgraded.
 enum ServerSystemType {
SYS_WINDOWS,
SYS_UNIX,
SYS_OTHERS
};

 /**//**
* Provide enough.
*/
class HLutil
 {
public :
static HLutil * getUtil();
~HLutil();

static QUrlInfo parseFileList( const QString & str, ServerSystemType systemType );

static QStringList seperateString( char sep, const QString & str );

private :
// To make it could not be create by instantiated.
 HLutil() {}
 HLutil( const HLutil & ) {}
 HLutil & operator =( const HLutil & util ) {}

// a static member to control single.
static UINT m_uNum;
static HLutil * m_pInstance;
};
}

#endif

 /**//*
* HLutil.cpp
* \date 2008-04-22
* \author HanYin
*/

#include "HLutil.h"

#include <QUrlInfo>
#include <QString>
#include <QChar>
#include <QStringList>
#include <QDebug>

namespace hy
  {
UINT HLutil::m_uNum = 0;
HLutil * HLutil::m_pInstance = NULL;

HLutil::~HLutil()
 {
if ( NULL != m_pInstance )
 {
delete m_pInstance;
m_pInstance = NULL;
m_uNum = 0;
}
}

HLutil * HLutil::getUtil()
 {
if ( m_uNum == 0 )
 {
m_uNum = 1;
m_pInstance = new HLutil();
}

return m_pInstance;
}

QUrlInfo HLutil::parseFileList( const QString & str, ServerSystemType systemType )
 {
QStringList strList = seperateString( ' ', str );
QUrlInfo url;

uint num = strList.size();
if ( num == 4 )
 {
if ( strList.at(2).toUpper() == QString("<DIR>").toUpper() )
 {
url.setDir(true);
}
else
 {
url.setFile(true);
url.setSize( strList.at(2).toUInt() );
}
url.setName( strList.at(3) );
}
else if ( num == 9 || num == 12 )
 {
QString temp = strList.at(0);
if ( temp[0]=='d' || temp[0]=='D' )
 {
url.setDir(true);
url.setName( strList.at(8) );
}
else if ( temp[0]=='-' )
 {
url.setFile(true);
url.setSize( strList.at(4).toUInt() );
url.setName( strList.at(8) );
}
else if ( temp[0]=='l' )
 {
url.setSymLink(true);
url.setDir(true);
url.setName( strList.at(11) );
}
else
 {
qDebug() << "HLutil::parseFileList( const QString & str, ServerSystemType systemType ) :\n"
"there is unrecognized URL - DIR" << endl;
return QUrlInfo();
}
}
else
 {
qDebug() << "HLutil::parseFileList( const QString & str, ServerSystemType systemType ) :\n"
"there is unrecognized SYS_TYPE" << endl;
return QUrlInfo();
}

return url;

}

QStringList HLutil::seperateString( char sep, const QString & str )
 {
QStringList strList;
strList.clear();

uint i=0, num = str.size();
QChar ch = str[0];
QString temp_string;
temp_string.clear();
while (true)
 {
if ( ch == ' ' || ch == '\t' )
 {
if ( ++i == num )
break;
ch = str[i];
continue;
}

temp_string.append(ch);
if ( ++i == num )
break;
ch = str[i];
if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' )
 {
strList.append(temp_string);
}
}

return strList;
}

}

Feedback
# re: 使用QTcpSocket实现FTP客户端的一个beta1.0版本(Qt4.3) 回复 更多评论
2008-04-26 23:08 by
# re: 使用QTcpSocket实现FTP客户端的一个beta1.0版本(Qt4.3) 回复 更多评论
2008-04-26 23:08 by
dddd
|