现在很多应用都需要上传与下载大型文件
通过HTTP方式上传大文件有一定的局限性
幸好FTP作为一个非常老而且非常成熟的协议可以高效稳定地完成大文件的上传下载
并且可以完美地实现续传
就拿我写的电影服务器管理端程序来说
各种方案比较后
发现使用FTP可以完美地实现要求
但是要通过WinSocket库实现FTP比较麻烦
幸好有Indy
一个包装了大多数网络协议的组件包
通过Indy程序设计人员可以通过阻塞方式进行编程可以抛开蹩脚的Winsocket异步模式采用与Unix系统上等同的阻塞编程模式进行这样程序员就可以很好的处理程序的运行流程 下面我们进入到Indy的TIdFtp世界
控件的说明
使用Indy 中的TIdFtp控件可以实现通过FTP方式进行文件的上传与下载
控件的具体使用
()控件属性设置
默认属性即可与服务器连接直接相关的属性如主机名与用户等在建立连接时进行设定需要设定的是RecvBufferSize和SendBufferSize两属性的值另外需要根据要传输的文件类型指定TransferType属性而其他属性按默认值设定即可
RecvBufferSize说明(默认值为字节)该属性为整型变量用于指定连接所用的接受缓沖区大小
SendBufferSize说明(默认值为字节)该属性也为整型变量用于指定连接所用的发送缓沖区的最大值该属性在WriteStream方法中时可用于TStream指定要发送内容的块数如果要发送的内容大于本属性值则发送内容被分为多个块发送
TransferType说明(默认值为ftBinary)该属性为TIdFTPTransferType型变量用于指定传输内容是二进制文件(ftBinary )还是ASCII文件(ftASCII)应用程序需要使用二进制方式传输可执行文件压缩文件和多媒体文件等而使用ASCII方式传输文本或超文本等文本型数据
()控件的事件响应
OnDisconnected响应TNotifyEvent类用于响应断开(disconnect)事件当Disconnect方法被调用用来关闭Socket的时候触发该响应应用程序必须指定该事件响应的过程以便对该断开事件进行响应
OnStatus响应TIdStatusEvent类该响应在当前连接的状态变化时被触发该事件可由DoStatus方法触发并提供给事件控制器属性axStatus是当前连接的TIdStatus值aaArgs是一个可选的参数用于格式化函数它将用于构造表现当前连接状态的文本消息
OnWork响应OnWord是TWorkEvent类事件的响应控制器OnWork用于关联DoWork方法当缓沖区读写操作被调用时通知Indy组件和类它一般被用于控制进度条和视窗元素的更新AWorkMode表示当前操作的模式其中wmRead组件正在读取数据wmWrite组件正在发送数据AWorkCount指示当前操作的字节计数
OnWorkBegin响应TWorkBeginEvent类当缓沖区读写操作初始化时该事件关联BeginWork方法用于通知Indy组件和类它一般被用于控制进度条和视窗元素的更新AWorkMode表示当前操作的模式其中wmRead组件正在读取数据wmWrite组件正在发送数据AWorkCountMax用于指示发送到OnWorkBegin事件的操作的最大字节数值代表未知
OnWorkEnd响应TWorkEndEvent类当缓沖区读写操作终止时该事件关联EndWork方法用于通知Indy组件和类AWorkMode表示当前操作的模式其中wmRead组件正在读取数据wmWrite组件正在发送数据AWorkCount表示操作的字节数
在事件响应中主要通过上述五种事件响应来控制程序在一般情况下在OnDisconnected中设定连接断开的界面通知在OnStatus中设定当前操作的状态在OnWork中实现传输中状态条和其他参数的显示而在OnWorkBegin和OnWorkEnd中分别设定开始传输和传输结束时的界面
()连接远程服务器
完成了设定控件属性和实现了控件的事件响应后就可以与服务器进行交互和传输了在连接之前应首先判断IdFtp是否处于连接状态如果Connected为False则通过界面控件或其他方式指定与服务器连接相关的一些TCP类属性的设置分别是Host(主机名):StringUsername(用户名):StringPassword(密码):String也可以指定Port(端口)之后调用Connect方法连接远程服务器如果无异常出现则连接成功建立
过程说明procedure Connect(AAutoLogin: boolean; const ATimeout: Integer);
该过程连接远程FTP服务器
属性AAutoLogin: boolean = True
连接后自动登录该参数默认为True
const ATimeout: Integer = IdTimeoutDefault
超时时间单位秒
示例代码
if IdFTPConnected then
try
if TransferrignData then IdFTPAbort;
IdFTPQuit;
finally
end
else
with IdFTP do try
Username := UserIDEditText;
Password := PasswordEditText;
Host := FtpServerEditText;
Connect;
ChangeDir(CurrentDirEditText);
finally
end;
()改变目录
连接建立后可以改变当前FTP会话所在的目录对于已知绝对路径的情况下可以直接调用ChangeDir(const ADirName: string)方法来转换目录ADirName表示服务器上的文件系统目录另外还可以调用ChangeDirUp回到上级目录
如果未知路径则可以通过List(ADest: TStrings; const ASpecifier: string; const ADetails: boolean)过程获取远程服务器的当前目录结构此时必须设定TransferType为ftASCII(ASCII模式)其中ADest保存当前目录结构可以在后续程序中调用该列表另外可以通过RetrieveCurrentDir方法获取当前目录名
过程说明
procedure ChangeDir(const ADirName: string);
改变工作目录
属性
const ADirName: string
远程服务器的目录描述
说明该过程实际上是实现了FTP CWD命令
procedure ChangeDirUp;
到上一级目录
function RetrieveCurrentDir: string;
该函数返回当前目录名
procedure List(ADest: TStrings; const ASpecifier: string; const ADetails: boolean);
列出当前目录所有文件和子目录及其属性
参数
ADest: TStrings
保存文件及子目录的返回结果
const ASpecifier: string =
文件掩码用于列出符合条件的文件
const ADetails: boolean = true
包含文件和子目录属性
property DirectoryListing: TIdFTPListItems;
返回文件及目录结构的列表
示例代码
LS := TStringListCreate;
try
IdFTPChangeDir(DirName);
IdFTPTransferType := ftASCII;
CurrentDirEditText := IdFTPRetrieveCurrentDir;
DirectoryListBoxItemsClear;
IdFTPList(LS);
DirectoryListBoxItemsAssign(LS);
if DirectoryListBoxItemsCount > then
if AnsiPos(total DirectoryListBoxItems[]) > then DirectoryListBoxItemsDelete();
finally
LSFree;
end;
()实现下载
在下载之前必须查看DirectoryListingItems[sCurrFile]ItemType是否为文件如返回为ditDirectory则代表当前文件名为目录不能下载必须导向到文件才可如为文件则可以进行下载在下载前设定传输的类型为二进制文件并且指定本地要保存的路径通过调用Get方法实现文件的下载下载过程较慢可以考虑将其放到线程中实现
过程说明
procedure Get(const ASourceFile: string; ADest: TStream; AResume: Boolean);
overload;
procedure Get(const ASourceFile: string; const ADestFile: string;
const ACanOverwrite: boolean; AResume: Boolean);
overload;
从远程服务器上获取文件
属性说明
const ASourceFile: string
远程服务器上的源文件名
const ADestFile: string
保存到客户机上的文件名
const ACanOverwrite: boolean = false
重写同名文件
AResume: Boolean = false;
是否进行断点续传
示例代码
SaveDialogFileName := Name;
if SaveDialogExecute then begin
SetFunctionButtons(false);
IdFTPTransferType := ftBinary;
BytesToTransfer := IdFTPSize(Name);
if FileExists(Name) then begin
case MessageDlg(File aready exists Do you want to resume the download operation?
mtConfirmation mbYesNoCancel ) of
mrYes: begin
BytesToTransfer := BytesToTransfer FileSizeByName(Name);
IdFTPGet(Name SaveDialogFileName false true);
end;
mrNo: begin
IdFTPGet(Name SaveDialogFileName true);
end;
mrCancel: begin
exit;
end;
end;
end
else begin
IdFTPGet(Name SaveDialogFileName false);
end;
()上传的实现
上传的实现与下载类似通过put方法即可
过程说明
procedure Put(const ASource: TStream; const ADestFile: string; const AAppend: boolean); overload;
procedure Put(const ASourceFile: string; const ADestFile: string; const AAppend: boolean); overload;
上传文件至服务器
属性说明
const ASourceFile: string
将要被上传的文件
const ADestFile: string =
服务器上的目标文件名
const AAppend: boolean = false
是否继续上传
代码示例
if IdFTPConnected then begin
if UploadOpenDialogExecute then try
IdFTPTransferType := ftBinary;
IdFTPPut(UploadOpenDialogFileName ExtractFileName(UploadOpenDialogFileName));
//可以在此添加改变目录的代码;
finally
//完成清除工作
end;
end;
()删除的实现
删除文件使用Delete方法该方法删除指定的文件删除对象必须为文件如果要删除目录则使用RemoveDir方法
过程说明
procedure Delete(const AFilename: string);
删除文件
procedure RemoveDir(const ADirName: string);
删除文件夹根据不同的服务器删除文件夹有不同的要求有些服务器不允许删除非空文件夹程序员需要添加清空目录的代码
上述两个过程的参数均为目标名称
代码示例
if not IdFTPConnected then exit;
Name := IdFTPDirectoryListingItems[iCurrSelect]FileName;
if IdFTPDirectoryListingItems[iCurrSelect]ItemType = ditDirectory then try
idftpRemoveDir(Name);
finally
end
else
try
idftpDelete(Name);
finally
end;
()后退的实现
后退在实际上是目录操作的一种可以简单的改变当前目录为来实现也可以通过回到上级目录来实现
()取消的实现
在IdFtp的传输过程中可以随时使用abort方法取消当前操作可以的OnWork事件的实现中来确定何时取消操作
代码示例
//取消按钮的OnClick响应
procedure TMainFormAbortButtonClick(Sender: TObject);
begin
AbortTransfer := true;
end;
//IdFTP的OnWork事件响应
procedure TMainFormIdFTPWork(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCount: Integer);
begin
if AbortTransfer then IdFTPAbort;
AbortTransfer := false;
end;
()断点续传的实现
断点续传就是在上传或下载过程开始时判断已经传输过的文件是否上传输完毕如果传输没有成功完成则在上次中断处继续进行传输工作实现该功能需要两个重要的操作首先是判断文件的大小信息其次是在传输过程Get和Put中指定上传的行为
判断服务器上文件的大小使用函数Size(FileName)在下载过程中比较本地文件和远程文件的信息然后在Get中指定AResume := True即可而上传也一样指定Put的AAppend := True就可以了
在前面我们讲过Indy的网络操作大部分是阻塞模式的TIdFtp也不例外这样在上述各个操作运行过程的时候用户界面被暂时冻结必须时要等待调用返回才能继续用户操作界面响应所以在实际编程中需要使用多线程的方式来保证户界面的响应Windows系统可以使用CreateThread系统调用来创建线程但是在使用的时候需要开发人员做很多额外的工作来保证线程的同步等问题而Indy中也包含了实现多线程的控件TIdThreadComponent相对比之下该控件实现多线程时更加方便也更容易控制