课程设计:用wininet接口写的FTP Client

题目1  FTP客户端

编写一个简单的FTP客户机程序,要求能够向FTP服务器发送命令,并接收FTP服务器返回的响应与数据。程序设计的具体要求如下:

1)要求实现的程序为图形化界面(如图下所示),可以输入FTP服务的相关信息(包括IP地址、用户名与密码),输出交互过程中的FTP命令与响应信息,以及从FTP服务器的根目录获得的文件(或目录)列表。

2)要求遵循RFC959的相关FTP协议规定。只要求实现USER、PASS、PASV、LIST、RETR和QUIT命令。点击Connect按钮,实现USER与PASS命令;点击List按钮,实现PASV 与LIST命令;点击Download按钮,实现PASV 与RETR命令;点击Quit按钮,实现QUIT命令。

3)支持IP地址、域名输入,及合法性检测,显示FTP登录过程,下载速率等。

4)支持多线程下载,实现线程管理,显示各线程的状态。报告各种异常,提示产生异常的原因。

5)要求有良好的编程规范与注释信息。

6)要求有详细的说明文档,包括程序的设计思想、工作流程、关键问题等。

7)要求在Windows操作系统环境中,建议使用Visual C++编程工具实现。



一.详细设计

我之所以选择使用wininet的接口,是因为用它来开发的话会比较简单和快速。

(1)界面布局

课程设计:用wininet接口写的FTP Client_第1张图片

(2)为Connect,List,Download,Upload,Quit,<-等button添加BN_CLICKED事件以及编写事件函数

(3)代码的编写集中CFtpClientDlg类里,下面就详细介绍一下这个类的代码:

1.几个常量:

//存储所有文件类型
const char fielType[8][9] = {"归档文件","文件夹","隐藏文件","普通文件","只读文件","系统文件","临时文件","其他文件"};
const int BUF_SIZE = 4096 ;
const int MSG_SIZE = 1024 ;

2.CFTPClientDlg的变量

        CFont edit_font;

CInternetSession m_InternetSession;  //定义一个会话对象
CFtpConnection *m_pFtpConnection;   //连接对象指针
HANDLE m_hEventKill;        //事件句柄


CString m_sFTPServerCurFolder;         //FTP服务器当前目录
CString m_sFTPServerParentFolder;    //FTP服务器当前目录的父目录


DWORD dwLastErrorMsg;                      //应答码
DWORD dwErrorMsgSize;     //应答信息长度
char  buf[MSG_SIZE];     //应答信息


CString strServer;                                    //服务器地址
        CString strRemoteFile;                          //选中的远程文件地址
CString strLocalFile;                              //存储在本地的文件地址
int     nItem;                                              //选中项的索引
int     selectedFileType;                         //选中文件的类型
long    fileLen;                                         //选中文件的长度
long    downloadSize;                           //当前下载量
bool    isSelected;                                 //是否选中 

3.CFTPClientDlg的构造函数

CFTPClientDlg::CFTPClientDlg(CWnd* pParent /*=NULL*/)
: CDialog(CFTPClientDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CFTPClientDlg)
m_Address = _T("");
m_Password = _T("");
m_User = _T("");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
edit_font.CreateFont(
18,   //字体大小
0,0,0,FW_NORMAL,  
FALSE,FALSE,  
0,  
ANSI_CHARSET,              // nCharSet
OUT_DEFAULT_PRECIS,        // nOutPrecision
CLIP_DEFAULT_PRECIS,       // nClipPrecision
DEFAULT_QUALITY,           // nQuality
        DEFAULT_PITCH | FF_SWISS, "Arial");

m_pFtpConnection = NULL;
m_hEventKill = CreateEvent (NULL, TRUE, FALSE, NULL);


m_sFTPServerCurFolder = "/";
m_sFTPServerParentFolder = "/";

dwLastErrorMsg = 0;
dwErrorMsgSize = MSG_SIZE;


strServer = "";
strRemoteFile = "";
strLocalFile = "";

nItem = 0;
selectedFileType = 0;
fileLen = 0;
downloadSize = 0;
        isSelected = FALSE;


// Set the timeout value to 10 seconds
m_InternetSession.SetOption (INTERNET_OPTION_CONNECT_TIMEOUT, 10000);
m_InternetSession.SetOption (INTERNET_OPTION_RECEIVE_TIMEOUT, 10000);
m_InternetSession.SetOption (INTERNET_OPTION_SEND_TIMEOUT, 10000);
}

4.CFTPClientDlg的地址解析函数

bool CFTPClientDlg::parsrAddress()
{
if(m_Address.IsEmpty())
{
AfxMessageBox("没有给出主机地址,请输入一个主机地址!");
return FALSE;
}
else
{
m_Interact_Info.AddString("状态: 正在解析"+m_Address+"的地址...");
DWORD dwServiceType;
CString strObject;
INTERNET_PORT nPort;
AfxParseURL(m_Address,dwServiceType,strServer,strObject,nPort);
if (strServer.IsEmpty())
strServer = m_Address;


hostent* phe = gethostbyname(strServer);
struct in_addr addr;

if(strServer.IsEmpty() && NULL == phe)
m_Interact_Info.AddString("错误: 非法地址");
else
{
addr.s_addr = *(u_long *) phe->h_addr_list[0];
char* ip = inet_ntoa(addr);
CString temp = "状态: 正在连接 ";
temp += ip;
temp += "...";
m_Interact_Info.AddString(temp);
}
phe = NULL;
return TRUE;
}
}

5.判断是否连接到FTP服务器

bool CFTPClientDlg::IsInitialized()
{
if (m_pFtpConnection != NULL)
return true;
else
return false;
}

6.获取服务器的响应信息,不过这个函数并不是经常奏效,我也没搞明白为什么有时候获取不到响信息

void CFTPClientDlg::GetResponseInfo()
{


::InternetGetLastResponseInfo(&dwLastErrorMsg,buf,&dwErrorMsgSize);


char info[100];
int i=0,j=0;
for( i =0;i<MSG_SIZE;i++)
{
if('\0' == buf[i] )
break;
if ('\n' != buf[i])
{
info[j] = buf[i];
j++;
}
else
{
info[j] = '\0';
m_Interact_Info.AddString(CString("响应: ") +info);
j=0;
}
}
}

7.登陆函数

bool CFTPClientDlg::Login (const CString &sHost, const CString &sUsername, const CString &sPassword, BOOL bPASV, int nPort, int nRetries, int nRetryDelay)
{
bool rr = false ;
int nRetryCount = 0 ;


if (m_pFtpConnection == NULL)
{
while (nRetryCount < nRetries)
{

//连接指定的FTP服务器
m_pFtpConnection = m_InternetSession.GetFtpConnection (sHost, sUsername, sPassword, nPort, bPASV);

if (m_pFtpConnection != NULL) //获取当前目录
{
m_Interact_Info.AddString("状态: 连接建立,等待欢迎消息...");
GetResponseInfo();
m_pFtpConnection->GetCurrentDirectory (m_sFTPServerCurFolder);
GetResponseInfo();
break;
}
m_Interact_Info.AddString("错误: 时间超时!");
nRetryCount ++;
}
}

rr = (nRetryCount < nRetries);


return rr ;

}

8.退出服务器函数

void CFTPClientDlg::Logoff()
{
if (m_pFtpConnection != NULL)
{
m_pFtpConnection->Close ();
GetResponseInfo();
delete m_pFtpConnection;
m_pFtpConnection = NULL;
m_sFTPServerCurFolder.Empty ();
m_Interact_Info.AddString("状态: 退出服务器!");
m_Interact_Info.SetTopIndex(m_Interact_Info.GetCount()-5);//将滚动条下拉到显示ListBox的最新内容
m_Dir_Info.DeleteAllItems();
}
}

9.设置FTP服务器的当前目录

bool CFTPClientDlg::ChangeCurrentDir(const CString &sRemoteDir)
{
bool rr = false ;


if (m_pFtpConnection != NULL)
{
m_pFtpConnection->SetCurrentDirectory(sRemoteDir);
GetResponseInfo();
rr = true ;
}


return rr ;
}

10.下载函数

bool CFTPClientDlg::Download (const CString &sRemoteFile, const CString &sLocalFile, bool pbAbort,DWORD dwTransferType)
{
bool rr = false ;
downloadSize = 0;


if (m_pFtpConnection != NULL)
{
CString sSource (sRemoteFile);
CString sDestPath (sLocalFile);


CFile file;

// 打开本地文件
if (file.Open(sDestPath, CFile::modeCreate | CFile::modeWrite, NULL))
{
//打开远程资源文件
CInternetFile* pInternetFile = m_pFtpConnection->OpenFile (sSource, GENERIC_READ, dwTransferType);
GetResponseInfo();
if (pInternetFile)
{
char buffer[BUF_SIZE];
unsigned int nRead = BUF_SIZE;
char temp[20];


//读取文件
while ( (nRead == BUF_SIZE) && (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT) && (!pbAbort) )
{
// read remote data into buffer
nRead = pInternetFile->Read (buffer, BUF_SIZE);
downloadSize += nRead ;//更新下载量
m_Dir_Info.SetItemText(nItem,3,ltoa(downloadSize,temp,10));//在列表控件里更新显示
this->UpdateWindow();
// write buffer to data file
file.Write (buffer, nRead);
}
GetResponseInfo();
//关闭远程资源文件
pInternetFile->Close ();
delete pInternetFile;


rr = true ;
}

// 关闭本地文件
file.Close ();
}
}


return rr ;
}

11.上传函数

bool CFTPClientDlg::Upload (const CString &sLocalFile, bool pbAbort, DWORD dwTransferType)
{
bool rr = false ;


if (m_pFtpConnection != NULL)
{
CFile file;

//打开本地文件
if (file.Open (sLocalFile, CFile::modeRead, NULL))
{
CInternetFile* pInternetFile = m_pFtpConnection->OpenFile (file.GetFileName (), GENERIC_WRITE);
GetResponseInfo();
if (pInternetFile)
{
//定义读写缓存
char buffer [BUF_SIZE];
unsigned int nRead = BUF_SIZE;

//读写文件
while ( nRead == BUF_SIZE && (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT) &&(!pbAbort) )
{
// read data into buffer
nRead = file.Read (buffer, BUF_SIZE);
// write buffer to remote data file
pInternetFile->Write (buffer, nRead);
}
GetResponseInfo();
//关闭远程资源文件
pInternetFile->Close();
delete pInternetFile;


rr = true ;
}


// 关闭本地文件
file.Close();
}
}


return rr ;
}

12.查找FTP服务器当前目录下的所有文件

bool CFTPClientDlg::GetFileList (const CString &sCurrentDir)
{
bool rr = false ;
CString sFileName;
char buf[20];
CString len;
int type;

if (m_pFtpConnection != NULL)
{
if (m_Dir_Info.GetItemCount()>0)
m_Dir_Info.DeleteAllItems();


CFtpFileFind ftpFind (m_pFtpConnection); //创建CFtpFileFind对象


BOOL bContinue = ftpFind.FindFile (sCurrentDir); //查找FTP当前目录下的文件
GetResponseInfo();
if (!bContinue)
{
ftpFind.Close ();
m_Interact_Info.AddString("错误: 未能在服务器找到文件!");
return rr;
}

int i = 0;
// 将所检索到的文件追加到列表控件中
while (bContinue)
{
bContinue = ftpFind.FindNextFile ();
GetResponseInfo();
sFileName = ftpFind.GetFileName();
m_Dir_Info.InsertItem(i,sFileName);//文件名
GetTypeOfFile(type,ftpFind);
if(1 != type )
{
len = ltoa(ftpFind.GetLength(),buf,10);
len += " B";
}
else
len = "";
m_Dir_Info.SetItemText(i,1,len);//文件大小
m_Dir_Info.SetItemText(i,2,fielType[type]);//文件类型
m_Dir_Info.SetItemData(i,type);
i++;
rr = true ;

}
if (0 == i)
m_Dir_Info.SetItemText(0,0,"空文件夹");
ftpFind.Close ();
}


return rr ;
}

13.获取文件类型函数

void CFTPClientDlg::GetTypeOfFile(int &type,const CFtpFileFind &fileFind)
{
if (fileFind.IsArchived())
type = 0;
else if (fileFind.IsDirectory()||fileFind.IsDots())
type = 1;
else if (fileFind.IsHidden())
type = 2;
else if(fileFind.IsNormal())
type = 3;
else if(fileFind.IsReadOnly())
type = 4;
else if(fileFind.IsSystem())
type = 5;
else if(fileFind.IsTemporary())
type = 6;
else
type = 7;
}

14.初始化列表控件

void CFTPClientDlg::initListCtrl()
{
m_Dir_Info.InsertColumn(0,"文件名",LVCFMT_LEFT,80);
m_Dir_Info.InsertColumn(1,"文件大小",LVCFMT_LEFT,100);
m_Dir_Info.InsertColumn(2,"文件类型",LVCFMT_LEFT,80);
m_Dir_Info.InsertColumn(3,"下载进度",LVCFMT_LEFT,80);
m_Dir_Info.SetExtendedStyle(LVS_EX_FULLROWSELECT);
}

15.点击List按钮的响应函数

void CFTPClientDlg::OnButtonList() 
{
// TODO: Add your control notification handler code here
if(isSelected)
{
           UpdateCurrentDir();
  m_Interact_Info.AddString("状态: 正在读取目录列表...");
  if(GetFileList(m_sFTPServerCurFolder))
m_Interact_Info.AddString("状态: 读取目录列表成功");
  else
m_Interact_Info.AddString("错误: 读取目录列表失败!");
  isSelected = FALSE;
  m_Interact_Info.SetTopIndex(m_Interact_Info.GetCount()-5);//将滚动条下拉到显示ListBox的最新内容
}
else
 AfxMessageBox("请选择文件!");
}

16.更新服务器的当前目录

void CFTPClientDlg::UpdateCurrentDir()
{
  if(1 == selectedFileType)//1代表文件夹
{
if ("/" == m_sFTPServerCurFolder.Right(1) )//如果当前目录以'/'结尾
m_sFTPServerCurFolder += strRemoteFile;
else
m_sFTPServerCurFolder += ("/" + strRemoteFile);
}
// AfxMessageBox(m_sFTPServerCurFolder);
ChangeCurrentDir(m_sFTPServerCurFolder);
}

17.点击Download按钮的响应函数

void CFTPClientDlg::OnButtonDownload() 
{
// TODO: Add your control notification handler code here
if(isSelected)
{
   CFileDialog saveDlg(FALSE,NULL,strRemoteFile,OFN_HIDEREADONLY |OFN_OVERWRITEPROMPT,NULL,this);


   if( IDOK == saveDlg.DoModal() )
   {
m_Interact_Info.AddString("状态: 开始下载 " + strRemoteFile + " ...");
if(1 != selectedFileType )
{
strLocalFile = saveDlg.GetPathName();
Download(strRemoteFile,strLocalFile);
m_Interact_Info.AddString("状态: 下载 " + strRemoteFile + "成功!");
}
else
{
AfxMessageBox("不支持下载文件夹!");
m_Interact_Info.AddString("错误: 下载 " + strRemoteFile + "失败!");
}
  m_Interact_Info.SetTopIndex(m_Interact_Info.GetCount()-5);//将滚动条下拉到显示ListBox的最新内容
  }
  isSelected = FALSE;
}
else
 AfxMessageBox("请选中文件");
}

18.点击目录信息ListCtrl的响应函数

void CFTPClientDlg::OnClickListDirInfo(NMHDR* pNMHDR, LRESULT* pResult) 
{
// TODO: Add your control notification handler code here
isSelected = TRUE;//设置选中
//Retrieves the position of the first selected list view item in a list view control
POSITION pos = m_Dir_Info.GetFirstSelectedItemPosition();
//Retrieves the index of a list view item position, and the position of the next selected list view item for iterating.
nItem = m_Dir_Info.GetNextSelectedItem(pos);


//获取远程文件名字
strRemoteFile = m_Dir_Info.GetItemText(nItem,0);
//选中文件的长度
fileLen = atol(m_Dir_Info.GetItemText(nItem,1));
//获取选中文件的类型
selectedFileType = m_Dir_Info.GetItemData(nItem);


*pResult = 0;
}

19.点击Quit按钮的响应函数

void CFTPClientDlg::OnButtonQuit() 
{
// TODO: Add your control notification handler code here
if(IsInitialized())
  Logoff();
else
  AfxMessageBox("请先登录服务器!");
}

20.点击Upload按钮的响应函数

void CFTPClientDlg::OnButtonUpload() 
{
// TODO: Add your control notification handler code here
CFileDialog uploadDlg(TRUE,NULL,NULL,OFN_HIDEREADONLY |OFN_OVERWRITEPROMPT,NULL,this);
if(IDOK == uploadDlg.DoModal())
{
strLocalFile = uploadDlg.GetPathName();
Upload(strLocalFile);
m_Interact_Info.SetTopIndex(m_Interact_Info.GetCount()-5);//将滚动条下拉到显示ListBox的最新内容
}
}

21.点击<-按钮的响应函数

void CFTPClientDlg::OnButtonParent() 
{
// TODO: Add your control notification handler code here
if("/" != m_sFTPServerCurFolder)
{
int index = m_sFTPServerCurFolder.ReverseFind('/');
if(-1 != index)
{
//截取字符串获得父目录
if(0 == index) index = 1;
m_sFTPServerParentFolder = m_sFTPServerCurFolder.Left(index);
m_sFTPServerCurFolder = m_sFTPServerParentFolder;
GetFileList(m_sFTPServerCurFolder);
}
}
}

二.运行结果截图

1.初始界面

课程设计:用wininet接口写的FTP Client_第2张图片

2.在Address编辑框里输入“ftp.microsoft.com”,然后点击Connect按钮

课程设计:用wininet接口写的FTP Client_第3张图片


3.点击List按钮

课程设计:用wininet接口写的FTP Client_第4张图片

4.点击Download按钮

课程设计:用wininet接口写的FTP Client_第5张图片


5.点击保存按钮,下载完成后

课程设计:用wininet接口写的FTP Client_第6张图片


6.点击Quit按钮


课程设计:用wininet接口写的FTP Client_第7张图片



三.小结

           写这些代码花了两天半,由于个人水平有限,所以代码比较粗糙,希望读者见谅。在写代码过程中,关于FTP client的主要过程,我主要是参考msdn的这篇文章http://msdn.microsoft.com/en-us/library/hf9x9wb4(v=vs.71).aspx,也在msdn查了不少类的api,当然少不了在google查资料咯。这个小程序主要实现了连接ft服务器,列出目录列表,下载和上传文件,退出服务器这些比较基础的功能,但还有相当多的不足,例如没有使用 CInternetException类来处理异常,不能多线程下载和上传,界面不美观,不能有效地获取服务器响应信息,还有重命名,删除服务器的文件等功能没有实现。

你可能感兴趣的:(ftp,ftp,mfc,mfc,WinINet,WinINet)