用IDFTP开发ftp客户端

(1)控件属性设置
RecvBufferSize说明(默认值为8192字节):该属性为整型变量,用于指定连接所用的接受缓冲区大小。
这些属性是indyftp里面的。

SendBufferSize说明(默认值为32768字节):该属性也为整型变量,用于指定连接所用的发送缓冲区的最大
值。该属性在WriteStream方法中 时,可用于TStream指定要发送内容的块数。如果要发送的内容大于本属性
值,则发送内容被分为多个块发送。

TransferType说明(默认值为ftBinary):该属性为TIdFTPTransferType型变量。用于指定传输内容是二进制
文件(ftBinary )还是ASCII文 件(ftASCII)。应用程序需要使用二进制方式传输可执行文件、压缩文件和
多媒体文件等;而使用ASCII方式传输文本或超文本等文本型数据 。

(2)控件的事件响应
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事件的操作的最大字节数,0值代表未知。

OnWorkEnd响应:TWorkEndEvent类。当缓冲区读写操作终止时,该事件关联EndWork方法用于通知Indy组件和类。AWorkMode表示当前操作的模式,其中:wmRead-组件正在读取数据;wmWrite-组件正在发送数据。AWorkCount表示操作的字节数。

在一般情况下,在OnDisconnected中设定连接断开的界面通知;在OnStatus中设定当前操作的状态;在OnWork中实现传输中状态条和其他参数 的显示;而在OnWorkBegin和OnWorkEnd中分别设定开始传输和传输结束时的界面。

好,下面是用到的 IdLogEvent的介绍。

事件:onReceived 就是已经接受到数据后应该做的事,而onReceive就是在接受过程中想做的事。

同理,onSent和onSend也是一样的道理。一般用onReceived和onSent.

好,下面是FTP客户端的实现了。其实FTP实现起来很简单,只要放IdFTP到界面上,然后进行连接后,就可以进行上传和下载了。当然,服务器要支持上传才行。

1在form的create写 IdFTPClient.Intercept :=IdLogEvent1;用于捕获Indy发送和接收的数据,在onreceived和onsent里面写如下代码:

procedure TFtpForm.IdLogEvent1Received
(ASender: TComponent; const AText,AData: String);
begin
ConnInfo('<<- ',AData);
end;
procedure TFtpForm.IdLogEvent1Sent(ASender: TComponent; const AText,AData: String);
begin
ConnInfo('->> ',AData);
end;
接收和发送数据时会把信息加到listbox里面。
ConnInfo是连接的信息,实现如下:
procedure TFtpForm.ConnInfo(Operation, S1: String);
Var
S: String;
begin
while Length(S1) > 0 do begin
  if Pos(#13, S1) > 0 then begin
    S := Copy(S1, 1, Pos(#13, S1) - 1);
    Delete(S1, 1, Pos(#13, S1));
    if S1[1] = #10 then Delete(S1, 1, 1);
    end
      else
      S := S1;
      ListBoxInfo.ItemIndex := ListBoxInfo.Items.Add(Operation + S);
        end;
      end;

然后写上程序的异常处理,我用的是ApplicationEvent组件
在ApplicationEvent的onexception里面写入WriteLog(E);把出现的异常记录在日志中。writelog的实现如下:

procedure TFTPForm.WriteLog(E:Exception);
const
FileNameExt = '.log';
  Path = 'log/' ;
var
  F:TextFile;
  DateStr,FileName,Buf:string;
begin
  DateTimeToString(DateStr,'yyyy-mm-dd',Now());
  FileName := Path+DateStr + FileNameExt;
  AssignFile(F,FileName);
  if FileExists(FileName) then
    Append(F)
  else
    Rewrite(F);
  try
    DateTimeToString(Buf,'yyyy-mm-dd hh:nn:ss:',Now());
    Buf := Buf+E.Message;
    Writeln(F,Buf);
    Application.ShowException(E);
  finally
    CloseFile(F);
  end;
end;

把产生的异常按日期生成文件。如果文件存在,则把异常写入,如果不存在,则创建文件。
好,外围的工作已经做好了,到了idftp的工作了。

连接FTP服务器端FTP客户端名为IdFTPClient设定FTP的服务器地址和端口,用户名和密码后就可以连接了。如果成功了,在listbox里面显示连接成功。
IdFTPClient.Host := Trim(EdtHost.Text);
    IdFTPClient.Port := StrToInt(Trim(EdtPort.Text));
    IdFTPClient.Username := Trim(EdtName.Text);
    IdFTPClient.Password := Trim(EdtPwd.Text);
    ListBoxInfo.Items.Add('连接到:'+EdtHost.Text);
    IdFTPClient.Connect();
    ListBoxInfo.Items.Add('登陆成功');
在IdFTP的OnStatus里面写 ListBoxInfo.ItemIndex := ListBoxInfo.Items.Add(aStatusText);显示连接的状态,并且此时会触发IdLogEvent的onReceived和onSent事件。

在连接了服务器后,需显示服务器端的文件,用下面的过程,不用IdFTP自带的List:

procedure TFtpForm.ViewRemoteList(Dir:string);
var
  DirList:TStringList;
  i:Integer;
  ListItem:TListItem;
  Attribute:TIdDirItemType;
begin
  DirList := TStringList.Create;
  try
    try
      IdFTPclient.ChangeDir(Dir);
      IdFTPClient.List(DirList);
      EdtDir.Text := IdFTPClient.RetrieveCurrentDir;
    except on E:Exception do
    begin
      WriteLog(E);
    end;
    end;
  finally
    DirList.Free;
  end;
  RemoteListView.Clear;
  for i:=0 to IdFTPClient.DirectoryListing.Count-1 do
  begin
    ListItem := RemoteListView.Items.Add();
    ListItem.Caption := IdFTPClient.DirectoryListing.FileName;
    ListItem.SubItems.Add(IntToStr(IdFTPClient.DirectoryListing.Size));
    ListItem.SubItems.Add(DateToStr(IdFTPClient.DirectoryListing.ModifiedDate));
    Attribute := IdFTPClient.DirectoryListing.ItemType;
    if Attribute=ditDirectory then
    begin
    ListItem.SubItems.Add('目录');
    ListItem.ImageIndex := 0;
    end
    else if Attribute=ditFile then
    begin
      ListItem.SubItems.Add('文件');
      ListItem.ImageIndex := 1;
    end
    else if Attribute=ditSymbolicLink then
    begin
      ListItem.SubItems.Add('其他');
      ListItem.ImageIndex := 2;
    end;

  end;
end;
断开FTP服务器连接   IdFTPClient.Disconnect();

好了,到了关键的上传和下载功能了。
上传功能,如果是上传文件的话:直接用idftp.put(....)就行了。
如果是上传文件夹的话,情况就复杂很多了。

if ShellTreeView1.SelectedFolder=nil then
  begin
  Messagebox(Handle,' 错误,没有选择文件夹/文件!','FTP客户端',MB_ICONERROR+MB_OK);
  Exit;
  end;//看有没有选择文件

FNameLs := TStringList.Create;
  for i:=0 to IdFTPClient.DirectoryListing.Count-1 do
  begin
  FNameLs.Add(IdFTPClient.DirectoryListing.Items.FileName);
  Application.ProcessMessages;
  end;//遍历FTP服务器端的文件,把所有文件的名称保存在 FNameLs里面,

//判断服务器端是否有同名的文件或文件夹,如果存在的话,提示是否覆盖,如果是的话,就删除文件/文件夹
for i:=0 to FNameLs.Count-1 do
  begin
    if FNameLs <> ShellTreeView1.SelectedFolder.DisplayName then
    continue
    else
    begin
      if Application.MessageBox('目录/文件已存在,是否替换?','提示',MB_OKCANCEL+MB_ICONQUESTION)=IDOK then
      begin
        if IdFTPClient.DirectoryListing.Items.ItemType=ditDirectory then
        try
          DelDir(IdFTPClient,FNameLs,FNameLs,IdFTPClient.RetrieveCurrentDir);
        finally
        end
        else
        try
          IdFTPClient.Delete(FNameLs);
        finally
        end;
      end
      else
        Exit;
    end;
  end;

//判断是否是文件夹,是的话,首先获取文件夹的大小
if ShellTreeView1.SelectedFolder.IsFolder then
FileSize := GetDirectorySize(ShellTreeView1.SelectedFolder.PathName);//然后上传文件夹
UpLoadDir(IdFTPClient.RetrieveCurrentDir,ShellTreeView1.SelectedFolder.PathName);

//获得文件夹大小 和 上传的函数如下:
function TFTPForm.GetDirectorySize(ADir: string): Integer;
var
Dir: TSearchRec;
Ret: integer;
Path: string;
begin
Result := 0;
Path := ExtractFilePath(ADir);
Ret := FindFirst(ADir, faAnyFile, Dir);
if Ret <> NO_ERROR then exit;
try
  while ret = NO_ERROR do
  begin
    inc(Result, Dir.Size);
    if (Dir.Attr in [faDirectory]) and (Dir.Name[1] <> '.') then
    Inc(Result, GetDirectorySize(Path + Dir.Name + '\*.*'));
    Ret := FindNext(Dir);
  end;
finally
  FindClose(Dir);
end;
end;

//参考网上的资料
//思路:传进来的有remotepath,表示是FTP服务器端的当前目录,localpath是想上传的文件名
根据上传文件名,在当前目录下创建文件夹,然后循环查找文件夹里面是否还有文件夹,有的话的继续创建,就是把文件夹全部创建出来。
所有的文件夹创建出来后,在一个个的上传文件。
procedure TFtpForm.UpLoadDir(RemotePath,LocalPath:string);
var
  strl1,strl2:TStringList;
  sr: TSearchRec;
  i,j,DirCount,FileCount:integer;
begin
  IdFTPClient.ChangeDir(RemotePath);
  DirCount := 0;
  FileCount := 0;
IdFTPClient.MakeDir(Copy(LocalPath,LastDelimiter('\',LocalPath)+1,Length(LocalPath)));

//创建文件夹  
//功能就是把当前文件夹里面的所有文件夹名称找出来
  if FindFirst(LocalPath+'\*.*',faDirectory,sr)=0 then //查找想上传的文件夹里面是否还有文件夹
  begin
    strl1 := TStringList.Create;
    repeat
      if(sr.Attr=faDirectory) and (sr.Name<>'.') and (sr.Name<>'..') then //如果是文件夹,并且名字不是'.'或者'..'
      begin
      strl1.Add(sr.Name);
      Inc(DirCount);
      end;

    until FindNext(sr) <> 0;
    FindClose(sr);
  end;

  for i:=0 to DirCount-1 do
  begin
    UpLoadDir(RemotePath+'/'+Copy(LocalPath,LastDelimiter('\',LocalPath)+1,Length(LocalPath)),LocalPath+'\'+strl1.Strings );
  end;
//找出所有的文件
  if FindFirst(LocalPath+'\*.*',faAnyFile,sr)=0 then
  begin
    strl2 := TStringList.Create;
    repeat
      if(sr.Attr<>faDirectory) then
      begin
      strl2.Add(sr.Name);
      Inc(FileCount);
      end;
    until FindNext(sr)<>0;
    FindClose(sr);
  end;
  IdFTPClient.ChangeDir(RemotePath+'/'+Copy(LocalPath,LastDelimiter('\',LocalPath)+1,Length(LocalPath)));
  for j:=0 to FileCount-1 do
  begin
    try
    IdFTPClient.Put(LocalPath+'\'+strl2[j],IdFTPClient.RetrieveCurrentDir+'/'+strl2[j]);
    Application.ProcessMessages;
    ListBoxInfo.Items.Add('上传成功!');
    except on E:Exception do
      begin
      ListBoxInfo.Items.Add('上传失败!');
      WriteLog(E);
      continue;
    end;
    end;
  end;
end;
//如果上传的是文件,取得文件的大小,取得大小主要是显示进度条。

if not ShellTreeView1.SelectedFolder.IsFolder then
FileSize := FileSizeByName(ShellTreeView1.SelectedFolder.PathName);//上传文件

IdFTPClient.Put (ShellTreeView1.SelectedFolder.PathName,IdFTPClient.RetrieveCurrentDir+'/'+ShellTreeView1.SelectedFolder.DisplayName);

文件/文件夹下载的步骤也是相似的。 文件的下载:
IdFTPClient.Get(Name,ShellTreeView1.Folders[ShellTreeView1.Selected.Index].PathName+'\'+Name,false,true);
文件夹的下载,都是用递归实现:
procedure TFTPForm.DownloadDir(var idFTP : TIdFtp;RemoteDir,LocalDir : string);
var
i,DirCount : integer;
begin
if not DirectoryExists(LocalDir + RemoteDir) then
  ForceDirectories(LocalDir+'\' + RemoteDir);
  idFTP.ChangeDir(RemoteDir);
  idFTP.List(nil);
  DirCount := idFTP.DirectoryListing.Count ;//获取所有文件或者文件夹的数量

  if DirCount = 0 then
  begin
    idFTP.ChangeDirUp;
    idFTP.List(nil);
  Exit;
  end;

for i := 0 to DirCount - 1 do
  begin
  if DirCount <> idFTP.DirectoryListing.Count then
  begin
    repeat
    idFTP.ChangeDirUp;
    idFTP.List(nil);
    Application.ProcessMessages;
    until DirCount = idFTP.DirectoryListing.Count ;
    end;

    if idFTP.DirectoryListing.ItemType = ditDirectory then //如果要下载的是文件夹         DownloadDir(idFTP,idFTP.DirectoryListing.FileName,LocalDir + RemoteDir + '\')
    else
    begin
      try
      idFTP.Get(idFTP.DirectoryListing.FileName,LocalDir + RemoteDir + '\' +
      idFTP.DirectoryListing.FileName,true); //下载文件
      except on E:Exception do
      begin
        ListBoxInfo.Items.Add('文件下载出错!');
        WriteLog(E);
      end;
      end;

    Application.ProcessMessages;
    if i = DirCount - 1 then
    begin
      idFTP.ChangeDirUp;
      idFTP.List(nil);
    end;
    end;
  end;
end;

//删除功能
文件的删除很简单,直接IdFTPClient.Delete(Name)就行了
procedure TFTPForm.DelDir(var idFTP : TIdFtp;RemoteDir,RootDir : string;const Path:string);
label Files;
var
i,DirCount : integer;
Temp,Temp2 : string;

  begin
  idFTP.ChangeDir(RemoteDir);
  if Pos(RootDir,idFTP.RetrieveCurrentDir) = 0 then Exit;

  Files :
    idFTP.List(nil);
    DirCount := idFTP.DirectoryListing.Count ;
    while DirCount = 0 do

    begin
    Temp := idFTP.RetrieveCurrentDir;
    idFTP.ChangeDirUp;
    idFTP.RemoveDir(Temp);
    idFTP.List(nil);
    Temp2 := idFTP.RetrieveCurrentDir;

      if Temp2=Path then Exit;
      DirCount := idFTP.DirectoryListing.Count ;
      for i := 0 to DirCount - 1 do

    if idFTP.DirectoryListing.FileName = RootDir then
      Exit;  
    end;

for i:= 0 to DirCount - 1 do
  begin
  if Pos(RootDir,idFTP.RetrieveCurrentDir) = 0 then
    Break ;
  if idFTP.DirectoryListing.ItemType = ditDirectory then
  begin
  try
    DelDir(idFTP,idFTP.DirectoryListing.FileName,RootDir,'0');
    Application.ProcessMessages;
    except on e:Exception do
    WriteLog(E);
    end;
  end

  else
  begin
    idFTP.Delete(idFTP.DirectoryListing.FileName);
    Application.ProcessMessages;
    goto Files ;
  end;
end;

end;

上一篇:解析MYSQL BINLOG 二进制格式(2)--FORMAT_DESCRIPTION_EVENT


下一篇:测试比json更快更小的二进制数据传输格式Msgpack [pythono MessagePack 版本]