正文:
一、FTP通信原理简述
1.1 FTP简介
FTP是基于TCP/IP协议的一个应用协议。主要实现在不同的计算机之间的数据共享。FTP 采用的是C/S模式。客户既可以下载文件也可以上传文件。当然,FTP给用户一定的权限。用户只能在权限下使用。目前,FTP的服务器种类很多,比如常用的SERV-U,客户端程序也很多,比如:CuteFTP。WINDOWS也提供了一个FTP客户程序。它们都根据相同的协议标准来设计的,具体协议内容可参考RFC文档。
SERV-U工作界面
windows提供的客户端
1.2 FTP工作原理
FTP工作原理与其它的应用协议有些不同。它是用两个端口进行通信的。一个端口用于命令交互。这个端口在用户连接之后一直保持;而另一个端口只是在数据传时打开(比如:上传文件,下载文件,获取服务端文件列表),在数据传输时有两种不同的模式,一是用户开通这个数据端口,这种模式叫做主动模式;二是服务器提供一个接口,这个模式叫被动模式。
FTP原理图
1.3 用户登录
FTP服务器提供了用户的访问权限,有的服务器可以匿名登录,有的服务器要求用户使用密码登录。在每一个与登录有关的命令时,服务器都会有一个返回信息。
下面显示了一个登录过程:
1.4 数据传输
在FTP中可以定义数据的传输格式,比如:二进制(进行图象和应用程序传输这种格式)。下面是一个传输过程:
二、FTP命令
在WINDOWS中提供的命令不是FTP的标准命令。有些命令是许多命令的合集。而FTP标准命令,每发送一个,服务器就会做出一个相应的动作,并把认证信息发送给用户。
具体的命令可以参照有关的资料
三、实例
在这里我们用一个FTP客户端来说明以上的知识。这里面主要是一个封装的类。
CFTPClient这个类实现的文件的上传与下载并能获得服务端文件的信息。
1.1 CFTPClient类
class
CFTPClient


...
{

//成员变量

private:

CSocket *m_pSocket;

CArchive *m_pRxarch;

CArchive *m_pTxarch;

CSocketFile *m_psfSokFile;

CString m_strMsg;//服务器发回的消息

CString m_fc;

CftpclientDlg *m_pWnd; //用于对窗口的操作

CByteArray m_btBuf;

//成员函数

public:

CFTPClient(void);

~CFTPClient(void);

//发送命令到服务器

BOOL FtpCommand (CString strCommand);

//登录到FTP服务器,这个函数只支持在没有防火墙的时候

BOOL LogOnToserver ( CString strHostname , int nHostPort , CString strUserName , CString strPassword );

//退出服务器

void LogOffServer();

//上传下载文件

BOOL MoveFile (CString strRemoteFile,CString strLocalFile , BOOL bPasv , BOOL bGet);

//列出文件列表

BOOL List();

void ProcessList();

//获取一行信息

BOOL GetLine(int ndx,CString &strLine);

//发送数据

BOOL WriteStr(CString strOutPut);

//接收数据

BOOL ReadStr();

//设置窗口

void SetWnd(CftpclientDlg *pWnd);

//发送信息

void SetMessage(CString strMsg);

//获取文件信息

BOOL GetFtpFileInfo(int ndx,FTP_FILE_INFO &ftpFileInfo);

protected:

//读取服务器发送的信息

BOOL ReadStr2();

//打开通道

BOOL OpenControlChannel(CString strServerHost , int nServerPort);

//关闭通道

void CloseControlChannel();

}
;

1.2
登录函数


I/**/////////////////////////////////////////////////////////
//

//
函数:BOOL CFTPClient::LogOnToserver ()

//

//
描述:

// 这个函数用于登录到FTP服务器,在这个函数没有对系统的防火墙作//
进一步分析,

//
读者可以进一步扩展它的功能

//

//

//
参数:

//
-strHostname 登录的主机名

//
-nHostPort 主机端口

//
-strUserName 用户名

//
-strPassword 用户密码

//
返回:

//
-BOOL 成功返回 TRUE 否则返回 FALSE

//

// //吴庆民 2005.4.19

/**////////////////////////////////////////////////////////
BOOL CFTPClient::LogOnToserver (CString strHostname,
int
nHostPort,CString strUserName,CString strPassword)


...
{

if (!this->OpenControlChannel (strHostname,21)) return FALSE;

if(!this->ReadStr ()) return FALSE;

this->SetMessage (this->m_strMsg );

//发送一个空消息

CString temp;

temp = "USER " + strUserName + " ";

//发送用户名

if (!this->WriteStr (temp))


...{

return FALSE;

}

if (!this->ReadStr ()) return FALSE;

this->SetMessage (this->m_strMsg );

//发送密码

temp = "PASS " + strPassword + " ";

if (!this->WriteStr (temp)) return FALSE;

if (!this->ReadStr ()) return FALSE;

this->SetMessage (this->m_strMsg );

return TRUE;

}

这个函数主要是联接服务器打开一个通道用于命令传输。这个通道是全双工的。

1.3
上传下载文件


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

//
函数:BOOL CFTPClient::MoveFile ()

//

//
描述:

//
上传或下载文件,不支持多线程,可以在这个函数上进行一下扩展

//

//

//
参数:

//
-strRemoteFile 远程文件名

//
-strLocalFile 本地文件名

//
-bPasv 是否为被动模式传输

//
-bGet 是否为下载文件

//

//
返回:

//
-BOOL 成功返回 TRUE 否则返回 FALSE

//

// //吴庆民 2005.4.19

/**///////////////////////////////////////////////////////////////////////
BOOL CFTPClient::MoveFile (CString strRemoteFile,CString strLocalFile,BOOL bPasv,BOOL bGet)


...
{

CFile flDataFile;

CString strCommand ;

int pos = 0;

UINT uServSock,uLocalSock;

CString strHost;

CSocket sServ;

CAsyncSocket asListen;

int i=0,j=0,num,numread,numsent;

CString strTemp;

const int BUFSIZE = 4096;

char cbuf[BUFSIZE];

if (!flDataFile.Open (strLocalFile,(bGet?CFile::modeCreate|CFile::modeWrite:CFile::modeRead)))


...{

this->SetMessage ("上传或下载的文件在本地不能打开!");

return FALSE;

}

//准备传输

strCommand = "TYPE I ";

if (!this->WriteStr (strCommand)) return FALSE;

if (!this->ReadStr ()) return FALSE;

this->SetMessage (this->m_strMsg );

if (bPasv)


...{

strCommand = "PASV ";

if (!this->WriteStr (strCommand)) return FALSE;

if (!this->ReadStr ()) return FALSE;

this->SetMessage (this->m_strMsg );

//if ((==-1&&(j=this->m_strMsg.Find ("/)"))==-1) return FALSE;

i=this->m_strMsg.Find ("(");

j=this->m_strMsg.Find (")");

if (i==-1||j==-1)


...{

this->SetMessage ("响应错误!");

}

strTemp = this->m_strMsg.Mid (i+1,(j-i)-1);

i = strTemp.ReverseFind (',');

uServSock = atol(strTemp.Right (strTemp.GetLength () - (i+1)));

strTemp = strTemp.Left (i);

//this->SetMessage (strTemp);

i = strTemp.ReverseFind (',');

uServSock += 256*atol(strTemp.Right (strTemp.GetLength () - (i+1)));

strHost = strTemp.Left (i);

while(1)


...{

if ((i=strHost.Find (','))==-1) break;

strHost.SetAt (i,'.');

}

//this->SetMessage (strHost);

//CString temp;

//temp.Format (strHost+" %d",uServSock );

//this->SetMessage (temp);

}

else


...{

if (!this->m_pSocket->GetSockName (strHost,uLocalSock)) return FALSE;

while(1)


...{

if ((i=strHost.Find ("."))==-1) break;

strHost.SetAt (i,',');

}

if (!(sServ.Create ())||!(sServ.Listen ())) return FALSE;

if(!sServ.GetSockName (strTemp,uLocalSock )) return FALSE;

strHost.Format (strHost+",%d,%d",uLocalSock/256,uLocalSock%256);

strCommand = "PORT " + strHost ;

strCommand += " ";

if (!(this->WriteStr (strCommand))) return FALSE;

if (!(this->ReadStr ())) return FALSE;

this->SetMessage (this->m_strMsg );

}

//发送下载或上传命令

if (bGet)


...{

strCommand = "RETR" + strRemoteFile;

strCommand += " ";

}

else


...{

strCommand = "STOR" + strRemoteFile;

strCommand += " ";

}

if (!this->WriteStr (strCommand)) return FALSE;

if (bPasv)


...{

if (!(asListen.Create ()) )

return FALSE;

asListen.Connect (strHost, uServSock);

}

if (!this->ReadStr ()) return FALSE;

this->SetMessage (this->m_strMsg );

if (this->m_fc != "1")


...{

this->SetMessage ("文件传输不成功!");

return FALSE;

}

if(!bPasv&&!sServ.Accept (asListen)) return FALSE;

//数据传输

DWORD lpArgument;

if (!asListen.AsyncSelect ()||!asListen.IOCtl (FIONBIO,&lpArgument)) return FALSE;

while(1)


...{

TRY


...{

if (bGet)


...{

if (!(num=asListen.Receive (cbuf,BUFSIZE,0)))

break;

else

flDataFile.Write (cbuf,num);

}

else


...{

if (!(numread = flDataFile.Read (cbuf,BUFSIZE)))

break;

else

if (!(numsent = asListen.Send (cbuf,numread,0))) break;

if (numread != numsent)

flDataFile.Seek (numsent - numread,CFile::current);

pos += numsent;

m_pWnd ->SetPos(pos);

}

}

CATCH(CException ,e)


...{

this->SetMessage ("数据传输过程中被中断!");

return FALSE;

}

END_CATCH

}

asListen.Close ();

flDataFile.Close ();

if (!this->WriteStr (" ")) return FALSE;

this->ReadStr ();

this->SetMessage (this->m_strMsg );

return TRUE;

}
参考资料:
1.Visual C ++ 网络通信协议分析与应用实现 汪晓平 钟军 人民邮电出版社
2. FTP 协议的分析和扩展 elly http://elly.blogdriver.com/index.jsp
3. RFC中 FTP 相关文档 http://www.ietf.org/rfc/