1、Delphi 使用 TIdFtp 控件实现 FTP 协议现在很多应用都需要上传与下载大型文件,通过 HTTP 方式上传大文件有一定的局限性。幸好 FTP 作为一个非常老而且非常成熟的协议可以高效稳定地完成大文件的上传下载,并且可以完美地实现续传。就拿我写的电影服务器管理端程序来说,各种方案比较后,发现使用 FTP 可以完美地实现要求。但是要通过 WinSocket 库实现 FTP 比较麻烦,幸好有Indy-一个包装了大多数网络协议的组件包。通过 Indy,程序设计人员可以通过阻塞方式进行编程,可以抛开蹩脚的 Winsocket 异步模式,采用与 Unix 系统上等同的阻塞编程模式进行。这样,程
2、序员就可以很好的处理程序的运行流程。 下面,我们进入到 Indy 的 TIdFtp 世界。 1.控件的说明使用 Indy 9 中的 TIdFtp 控件可以实现通过 FTP 方式进行文件的上传与下载。2.控件的具体使用(1)控件属性设置默认属性即可,与服务器连接直接相关的属性如主机名与用户等在建立连接时进行设定。需要设定的是 RecvBufferSize 和 SendBufferSize 两属性的值。另外需要根据要传输的文件类型指定 TransferType 属性,而其他属性按默认值设定即可。RecvBufferSize 说明(默认值为 8192 字节):该属性为整型变量,用于指定连接所用的接受
3、缓冲区大小。SendBufferSize 说明(默认值为 32768 字节):该属性也为整型变量,用于指定连接所用的发送缓冲区的最大值。该属性在 WriteStream 方法中时,可用于 TStream 指定要发送内容的块数。如果要发送的内容大于本属性值,则发送内容被分为多个块发送。TransferType 说明(默认值为 ftBinary):该属性为 TIdFTPTransferType 型变量。用于指定传输内容是二进制文件(ftBinary )还是 ASCII 文件( ftASCII) 。应用程序需要使用二进制方式传输可执行文件、压缩文件和多媒体文件等;而使用 ASCII 方式传输文本或超
4、文本等文本型数据。(2)控件的事件响应OnDisconnected 响应:TNotifyEvent 类,用于响应断开(disconnect)事件。当Disconnect 方法被调用用来关闭 Socket 的时候,触发该响应。应用程序必须指定该事件响应的过程,以便对该断开事件进行相应。OnStatus 响应:TIdStatusEvent 类。该响应在当前连接的状态变化时被触发。该事件可由 DoStatus 方法触发并提供给事件控制器属性。 axStatus 是当前连接的 TIdStatus 值;aaArgs 是一个可选的参数用于格式化函数,它将用于构造表现当前连接状态的文本消息。OnWork 响
5、应: OnWord 是 TWorkEvent 类事件的响应控制器。OnWork 用于关联DoWork 方法当缓冲区读写操作被调用时通知 Indy 组件和类。它一般被用于控制进度条和视窗元素的更新。AWorkMode 表示当前操作的模式,其中:wmRead-组件正在读取数据;wmWrite-组件正在发送数据。AWorkCount 指示当前操作的字节计数。OnWorkBegin 响应:TWorkBeginEvent 类。当缓冲区读写操作初始化时,该事件关联BeginWork 方法用于通知 Indy 组件和类。它一般被用于控制进度条和视窗元素的更新。AWorkMode 表示当前操作的模式,其中:wm
6、Read- 组件正在读取数据;wmWrite- 组件正在发送数据。AWorkCountMax 用于指示发送到 OnWorkBegin 事件的操作的最大字节数,0 值代表未知。OnWorkEnd 响应:TWorkEndEvent 类。当缓冲区读写操作终止时,该事件关联EndWork 方法用于通知 Indy 组件和类。AWorkMode 表示当前操作的模式,其中:wmRead-组件正在读取数据;wmWrite-组件正在发送数据。AWorkCount 表示操作的字节数。在事件响应中,主要通过上述五种事件响应来控制程序。在一般情况下,在OnDisconnected 中设定连接断开的界面通知;在 OnS
7、tatus 中设定当前操作的状态;在OnWork 中实现传输中状态条和其他参数的显示;而在 OnWorkBegin 和 OnWorkEnd 中分别设定开始传输和传输结束时的界面。(3)连接远程服务器完成了设定控件属性和实现了控件的事件响应后,就可以与服务器进行交互和传输了。在连接之前,应首先判断 IdFtp 是否处于连接状态,如果 Connected 为 False,则通过界面控件或其他方式指定与服务器连接相关的一些 TCP 类属性的设置,分别是: Host(主机名):String、 Username(用户名) :String、Password(密码):String ,也可以指定 Port(端
8、口)。之后调用 Connect 方法连接远程服务器,如果无异常出现则连接成功建立。过程说明:procedure Connect(AAutoLogin: boolean; const ATimeout: Integer);该过程连接远程 FTP 服务器属性:AAutoLogin: boolean = True连接后自动登录,该参数默认为 True。const ATimeout: Integer = IdTimeoutDefault 超时时间,单位:秒。示例代码:if IdFTP1.Connected then tryif TransferrignData then IdFTP1.Abort;Id
9、FTP1.Quit;finallyendelsewith IdFTP1 do tryUsername := UserIDEdit.Text;Password := PasswordEdit.Text;Host := FtpServerEdit.Text;Connect;ChangeDir(CurrentDirEdit.Text);finallyend; (4)改变目录连接建立后,可以改变当前 FTP 会话所在的目录。对于已知绝对路径的情况下,可以直接调用 ChangeDir(const ADirName: string)方法来转换目录,ADirName 表示服务器上的文件系统目录,另外还可以调
10、用 ChangeDirUp 回到上级目录。如果未知路径,则可以通过 List(ADest: TStrings; const ASpecifier: string; const ADetails: boolean)过程获取远程服务器的当前目录结构,此时必须设定 TransferType 为ftASCII(ASCII 模式) ,其中:ADest 保存当前目录结构,可以在后续程序中调用该列表。另外可以通过 RetrieveCurrentDir 方法获取当前目录名。过程说明:procedure ChangeDir(const ADirName: string); 改变工作目录属性const ADirN
11、ame: string 远程服务器的目录描述说明:该过程实际上是实现了 FTP CWD 命令。procedure ChangeDirUp; 到上一级目录function RetrieveCurrentDir: string; 该函数返回当前目录名procedure List(ADest: TStrings; const ASpecifier: string; const ADetails: boolean); 列出当前目录所有文件和子目录及其属性参数:ADest: TStrings 保存文件及子目录的返回结果const ASpecifier: string = 文件掩码,用于列出符合条件的文件
12、const ADetails: boolean = true 包含文件和子目录属性property DirectoryListing: TIdFTPListItems; 返回文件及目录结构的列表示例代码:LS := TStringList.Create;tryIdFTP1.ChangeDir(DirName);IdFTP1.TransferType := ftASCII;CurrentDirEdit.Text := IdFTP1.RetrieveCurrentDir;DirectoryListBox.Items.Clear;IdFTP1.List(LS);DirectoryListBox.It
13、ems.Assign(LS);if DirectoryListBox.Items.Count 0 thenif AnsiPos(total, DirectoryListBox.Items0) 0 then DirectoryListBox.Items.Delete(0);finallyLS.Free;end; (5)实现下载在下载之前,必须查看 DirectoryListing.ItemssCurrFile.ItemType 是否为文件,如返回为 ditDirectory 则代表当前文件名为目录,不能下载,必须导向到文件才可。如为文件,则可以进行下载。在下载前,设定传输的类型为二进制文件,并且
14、指定本地要保存的路径。通过调用 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 ASourceFil
15、e: string 远程服务器上的源文件名const ADestFile: string 保存到客户机上的文件名const ACanOverwrite: boolean = false 重写同名文件AResume: Boolean = false 是否进行断点续传示例代码:SaveDialog1.FileName := Name;if SaveDialog1.Execute then beginSetFunctionButtons(false);IdFTP1.TransferType := ftBinary;BytesToTransfer := IdFTP1.Size(Name);if Fil
16、eExists(Name) then begincase MessageDlg(File aready exists. Do you want to resume the download operation?,mtConfirmation, mbYesNoCancel, 0) ofmrYes: beginBytesToTransfer := BytesToTransfer - FileSizeByName(Name);IdFTP1.Get(Name, SaveDialog1.FileName, false, true);end;mrNo: beginIdFTP1.Get(Name, Save
17、Dialog1.FileName, true);end;mrCancel: beginexit;end;end;endelse beginIdFTP1.Get(Name, SaveDialog1.FileName, false);end;(6)上传的实现上传的实现与下载类似,通过 put 方法即可。过程说明:procedure Put(const ASource: TStream; const ADestFile: string; const AAppend: boolean); overload;procedure Put(const ASourceFile: string; const A
18、DestFile: string; const AAppend: boolean); overload; 上传文件至服务器属性说明:const ASourceFile: string 将要被上传的文件const ADestFile: string = 服务器上的目标文件名const AAppend: boolean = false 是否继续上传代码示例:if IdFTP1.Connected then beginif UploadOpenDialog1.Execute then tryIdFTP1.TransferType := ftBinary;IdFTP1.Put(UploadOpenDi
19、alog1.FileName, ExtractFileName(UploadOpenDialog1.FileName);/可以在此添加改变目录的代码;finally/完成清除工作end;end; (7)删除的实现删除文件使用 Delete 方法,该方法删除指定的文件,删除对象必须为文件。如果要删除目录则使用 RemoveDir 方法。过程说明:procedure Delete(const AFilename: string); 删除文件procedure RemoveDir(const ADirName: string); 删除文件夹,根据不同的服务器删除文件夹有不同的要求。有些服务器不允许删
20、除非空文件夹,程序员需要添加清空目录的代码。上述两个过程的参数均为目标名称代码示例:if not IdFTP1.Connected then exit;Name := IdFTP1.DirectoryListing.ItemsiCurrSelect.FileName;if IdFTP1.DirectoryListing.ItemsiCurrSelect.ItemType = ditDirectory then tryidftp1.RemoveDir(Name);finallyendelsetryidftp1.Delete(Name);finallyend; (8)后退的实现后退在实际上是目录操
21、作的一种,可以简单的改变当前目录为来实现,也可以通过回到上级目录来实现。(9)取消的实现在 IdFtp 的传输过程中,可以随时使用 abort 方法取消当前操作。可以的 OnWork 事件的实现中来确定何时取消操作。代码示例:/取消按钮的 OnClick 响应procedure TMainForm.AbortButtonClick(Sender: TObject);beginAbortTransfer := true;end;/IdFTP 的 OnWork 事件响应procedure TMainForm.IdFTP1Work(Sender: TObject; AWorkMode: TWorkM
22、ode;const AWorkCount: Integer);begin. if AbortTransfer then IdFTP1.Abort;AbortTransfer := false;end; (10)断点续传的实现断点续传就是在上传或下载过程开始时,判断已经传输过的文件是否上传输完毕,如果传输没有成功完成,则在上次中断处继续进行传输工作。实现该功能需要两个重要的操作,首先是判断文件的大小信息,其次是在传输过程 Get 和 Put 中指定上传的行为。判断服务器上文件的大小使用函数 Size(FileName)。在下载过程中,比较本地文件和远程文件的信息,然后在 Get 中指定 ARes
23、ume := True 即可。而上传也一样,指定 Put 的AAppend := True 就可以了。 在前面我们讲过,Indy 的网络操作大部分是阻塞模式的,TIdFtp 也不例外。这样在上述各个操作运行过程的时候用户界面被暂时冻结,必须要等待调用返回才能继续用户操作界面响应。所以在实际编程中,需要使用多线程的方式来保证户界面的响应。Windows 系统可以使用 CreateThread 系统调用来创建线程,但是在使用的时候需要开发人员做很多额外的工作来保证线程的同步等问题。而 Indy 中也包含了实现多线程的控件TIdThreadComponent,相对比之下该控件实现多线程时更加方便,也更容易控制。