TIdFTP控件多线程FTP下载(Indy 10)(200)

  • 主题发起人 主题发起人 zhmymichael
  • 开始时间 开始时间
Z

zhmymichael

Unregistered / Unconfirmed
GUEST, unregistred user!
各位富翁前辈,我现在在做TIdFTP多线程Ftp下载,目前遇到的问题是:1、线程并不是同步执行,而是等到一个线程执行完毕之后,另一个线程再运行。2、每一个线程下载的文件不是预先设定的大小,而是整个文件的大小,从而导致有多少线程下载多少遍文件。我的线程执行函数和下载函数如下:我在thread.execute函数中 CS.Enter; //进入临界区 synchronize DownLoadFile; CS.Leave;互斥的去调用DownloadFile函数,其内容如下://下载函数procedure MyThread.DownLoadFile();var tStream: TFileStream; TIdFTP1: TIdFTP; n1,n2:integer;begin //ftp方式下载 TIdFTP1 := TIdFTP.Create(nil); frmModel.IdAntiFreeze1.OnlyWhenIdle:=False;//设置使程序有反应 if FileExists(temFileName) then //建立文件流 begin tStream := TFileStream.Create(temFileName, fmOpenWrite); tResume := True; end else tStream := TFileStream.Create(temFileName, fmCreate); TIdFTP1.Username:= frmModel.UserIDEdit.Text; TIdFTP1.Password:= frmModel.PasswordEdit.Text; TIdFTP1.Host := frmModel.sIP; TIdFTP1.Connect; TIdFTP1.ChangeDir('zmy'); TIdFTP1.TransferType := ftBinary; frmModel.BytesToTransfer:=tlast-tstart; TIdFTP1.OnWorkBegin := frmModel.IdFTP1WorkBegin; TIdFTP1.OnWork := frmModel.IdFTP1Work; TIdFTP1.OnWorkEnd := frmModel.IdFTP1WorkEnd; TIdFTP1.sendCmd('rest 100/r/n'); TIdFTP1.sendCmd('SIZE '+frmModel.sFileName+'/r/n'); TIdFTP1.sendCmd('PASV/r/n'); TIdFTP1.SendCmd('TYPE I'+'/r/n'); TIdFTP1.SendCmd('rest '+inttostr(tstart)+'/r/n'); //下载的起始点 TIdFTP1.sendCmd('RETR '+frmModel.sFileName+'/r/n'); try if tResume then //续传 begin tStream.Position := tStream.Size; TIdFTP1.SendCmd('rest '+inttostr(tStream.Position)+'/r/n'); //下载的起始点 TIdFTP1.Get(frmModel.sFileName, tStream, True); end else begin TIdFTP1.Get(frmModel.sFileName, tStream, True); end; finally tStream.Free; end;end;恳请高手解答,如果有完整的例子更好。我全部的家当200分就这些了。麻烦各位富翁了。(在线等)
 
感觉是楼主对多线程理解不够,1.在thread.execute函数中 CS.Enter; //进入临界区 synchronize DownLoadFile; CS.Leave;你这里把整个下载就用CS包围起来,变成单线程了,应该去掉,应该在保存文件或多个线程下载同一个文件,写入到同一个内存流中时,才用CS.Enter的形式保护.2. TIdFTP1.OnWorkBegin := frmModel.IdFTP1WorkBegin; TIdFTP1.OnWork := frmModel.IdFTP1Work; TIdFTP1.OnWorkEnd := frmModel.IdFTP1WorkEnd;这里事件这样赋没意义,有在线程里真接操作界面的嫌疑,应该写个OnWorkBegin过程,如MyOnWorkBegin...再赋值TIdFTP1.OnWorkBegin := MyOnWorkBegin然后在MyOnWorkBegin里用Synchronize()调用显示过程,把状态显示在窗体界面上.3.frmModel.IdAntiFreeze1.OnlyWhenIdle:=False;//设置使程序有反应这个去掉,多线程不需要用IdAntiFreeze1,IdAntiFreeze1是单线程防止界面无响应该才用4.总的就是:a).多个线程要真正操作同一段内存的那一刻,才用CS保护,而且里面的代码尽可能的短.b).线程中真正操作窗体界面的那一刻,才用Synchronize()调用,这时实际上就是在主线程中执行,所以里面的代码也要尽可能的短.
 
谢谢你的回答,感觉有点明白了,那麻烦您能不能提供一点详细的代码上来,本人感激不尽啊。即使我设置了多线程,怎么样读取FTP上文件的一部分,从而实现每个线程只读取文件的一段,从而下载整个文件.(我已经设置了每个线程应该读取的起始点和终结点,但结果不是我想要的,因为每个线程都下载了整个文件。怎么办?)
 
顺着看了一下INDY的源码(INDY9),大致整理一下:1.Get有两个方法1.1).procedure TIdFTP.Get(const ASourceFile: string; ADest: TStream; AResume: Boolean = False);1.2).procedure TIdFTP.Get(const ASourceFile, ADestFile: string; const ACanOverwrite: Boolean = False; AResume: Boolean = False);这里1.2是调用1.1的,若要断点下载,只要调用Get时先设置ADest.Position属性就可以了,IDFTP会自动从这个位置开始下载,往下看就知道了.2.再看1.1的Get方法是调用下面这个来下载的InternalGet('RETR ' + ASourceFile, ADest, AResume); {Do not translate}3.再看InternalGet过程,if AResume then //这里就是自动设置起始的下载点begin SELF.SendCmd('REST ' + IntToStr(ADest.Position), [350]); {Do not tranlsate}end;SELF.WriteLn(ACommand); //这里就是RETR下载文件的命令,ReadStream(ADest, -1, true); //这里真正开始下载注意ReadStream(ADest, -1, true);这个过程的第二个参数-1,表示要下载的字节数未知,此时默认是字节数是High(integer),第三个参数true,表示一真下载直到断开.所以IDFTP只能指定从起始下载点,并不能指定结束点,所以并不能真正意义上实现多线程同时下载同一个文件.现在就有一个问题了,假如我有一个文件9MB要下载,我想让三个线程同时下载这个文件,第一个线程负责0-3MB部分,第二个线程负责3-6MB部分,第三个线程负责6-9MB部分,这样三个下载加起来刚好就是9MB,达到同时下载,但按刚才分析,显然实现不了这个.有没有解决方法呢,当然有,那就是修改INDY源码,关键就是让ReadStream(ADest, -1, true);的第二个参数传入真正要下载的字节数,并把第三个参数设为false先看看INDY已经提供的好用的方法或属性IDFTP1.CanResume: Boolean;判断服务器是否支持断点续传IDFTP1.Size()取得服务器上的要下载的文件的大小,然把这个大小进行分割,并且把它作为参数传入IDFTP1.Get(),所以,只要改写Get方法,让它多一个参数就可以了,这个参数就是每个线程真正要下载的字节数LByteCount,这样,INDY就不会每个线程都一律下载到最后断开.只要修改这两个过程:Get()和InternalGet(),让它们都接受LByteCount参数,同时把InternalGet()过程里的ReadStream(ADest, -1, true)改成ReadStream(ADest, LByteCount, false); //注意这里,跟上面讲的源码就改掉了.若分三个线程下载同一个文件的话,我会先创建三个内存流缓冲区,每个下载完成了就事件能数一个管理中心,当管理中心检查三个线程都下载完成了,再保存到硬盘,当然管理中心也可以写得复杂一些,下载一部分就保存一部也可以的.所以,调用IDFTP1.Get()时不要传本地文件名,面是传入一个TStream的句柄,并设好起始下载点Position,因为传文件名的话,IDFTP会自动保存在硬盘,多个线程可能就在保存时会冲突了.经过以上改进后,我觉得才是真正意文上的线程了并且自由控制,再复杂一些,多个线程下载时,有些线程先下载完了,有的线程还在下载而且剩比较多时,此时又可以动态调节下载点,把第三个线程的再分一点给第一个线程进行下载,自由发挥吧.
 
再说楼主的代码 TIdFTP1.SendCmd('rest '+inttostr(tstart)+'/r/n'); //下载的起始点 TIdFTP1.sendCmd('RETR '+frmModel.sFileName+'/r/n');这两行就不要自已写了IDFTP会自行处理的.还有就是下载前要先判断服务器是否把持断点续传
 
谢谢 djrj ,我来看看,如果有问题,我还希望您能帮帮我,呵呵,接分。
 
后退
顶部