Delphi处理高速文件上传下载的代码及思路
上传和下载是一对方向不同的概念,下面对应的客户端和服务器代码:掉个头,它就是下载;再掉个头,它就是上传。
一、思路
1、将大文件:分段(即常说的“断点”上传或下载)上传或下载
2、分别上传或下载这些分段
3、将上传或下载后的各“分段”文件流合并还原
4、关于加速(网上你经常看到“高速”上传或下载):若多线程分别上传或下载这些分段,即可提速。
你们去使用 高勇老师的代码,它是完全无问题;这里仅仅给大家提供一种提速的方案,高勇老师的代码,你按照这个思路稍作改装,即可高速上传或下载。
二、代码
2.1、客户端:
///<summary>RestFull上传文件函数(文件流方法):///上传单个文件(TFileStream实例化抽象流:文件分段传参给服务器):///</summary>
function CopyFileTFileStream(const Path: string;const BufSize :Integer= (1*1024*1024); //:BufSize大小默认值1MB:为1个分段const ServerPath:string='api\upload\';const ServerFileName:string='A1.pdf' ):string;
var Buffer: TBytes; ReadCount,FileSize,FileBufferCount,iCircle: Integer;ATStream,AWriteStream: TFileStream; ACopyStream:TStream;AResultStr:string; ifResult: Boolean;
beginif (Path.Trim='') or (ServerPath.Trim='') or (ServerFileName.Trim='') thenbeginAResultStr:='文件不能为空!';Result := AResultStr ; exit; end;if FileExists(Path) =true thenbeginAResultStr:='文件存在!';//AWriteStream:读本地大文件的流:AWriteStream := TFileStream.Create( Path,fmOpenRead );iCircle:=ceil( AWriteStream.Size /BufSize );//:分几段读取,就写几个临时文件.tmp://大文件:已知大于最小设定值,则分段读取,直到读完为止:if AWriteStream.Size > BufSize then//:不能用FileSize显式的判断begin //:需要分数据包处理,如果是服务端是Datasnap默认32k的缓冲区(缓存,太JB小啦)://文件流无需SetSize,而是设置其Buffer:TBytes字节数组的长度://SetLength(Buffer, BufSize);SetLength( Buffer,(5*1024*1024) ); //:如果设置的较大:超内存需要控制,单独写例程//$F000;//$F000:61440:60kb//:System.Classes.fmCreate//:印象中好像最大缓冲区就这个大小,你自己核实吧,5兆我够了FileBufferCount:=0; FileSize:=BufSize;AResultStr:=''; for FileBufferCount := 1 to iCircle dobegin //文件流无需SetSize://ATStream:将大文件流AWriteStream拆分成://三位数的小临时文件.tmp的文件流:ACopyStream:= TMemoryStream.Create;ATStream:=TFileStream.Create( Path+FillBeforeString(IntToStr(FileBufferCount),10,'0')+'.tmp',fmCreate or fmOpenReadWrite);//:FillBeforeString:自写函数,字符串末尾向左补齐字符//:文件流,没有就产生、有就打开 //10位:足够1T的文件拆分tryif FileSize < AWriteStream.Size then FileSize:= BufSize;if FileSize >= AWriteStream.Size thenFileSize:=AWriteStream.Size - (iCircle-1) * BufSize;ReadCount := AWriteStream.Read( Buffer[0], FileSize );AResultStr:=AResultStr+','+IntToStr(FileSize); if ReadCount > 0 thenATStream.WriteBuffer(Buffer[0], ReadCount);ACopyStream.CopyFrom(ATStream,0);//:ACopyStream:由CopyFrom的调用者自己来释放try ifResult:=ServerMethods.upLoadFileFromTStreamBufferRead(ACopyStream,( ServerPath +ServerFileName+FillBeforeString(IntToStr(FileBufferCount),3,'0')+'.tmp') );//:服务器方法成功执行需要时间://while ifResult<>true do sleep(0);//:需要的场景才这样做exceptAWriteStream.disposeOf; ATStream.disposeOf; ACopyStream.disposeOf; Result := '流加载错误'; exit;end; FileSize:=FileSize + BufSize;finally//传完该临时分段文件流释放它的句柄,并删除它对应的临时文件:ATStream.disposeOf; System.IOUtils.TFile.Delete(Path+FillBeforeString(IntToStr(FileBufferCount),3,'0')+'.tmp');end;end;//确保:全部临时分段文件流处理完成之后,发出合并文件请求,//将分段文件流还原为原始文件:if FileBufferCount =iCircle thenbegin try ifResult:=ServerMethods.mergeFilesUseTFileStream(ServerPath + ServerFileName);//while ifResult <>true do sleep(0);//:服务器方法成功执行需要时间,需要的场景才做exceptAWriteStream.disposeOf; ATStream.disposeOf; ACopyStream.disposeOf; Result := '文件流合并错误'; exit;end;AResultStr:= '文件上传成功';AWriteStream.disposeOf;end;end elsebegin//小文件:已知小于最小设定值,则直接复制数据:ACopyStream:= TMemoryStream.Create;ACopyStream.CopyFrom(AWriteStream,0);try ifResult:=ServerMethods.upLoadFileFromTStreamBufferRead( ACopyStream,(ServerPath+ServerFileName) );//:服务器方法成功执行需要时间//while ifResult<>true do sleep(0);//:需要的场景才这样做AResultStr:=IntToStr(AWriteStream.Size);exceptAWriteStream.disposeOf; ACopyStream.disposeOf; Result := '服务器出错了'; exit;end;if ifResult=true thenbeginAResultStr:= '文件上传成功';AWriteStream.disposeOf; end;end;end else AResultStr:='文件不存在!';System.IOUtils.TDirectory.SetCurrentDirectory(System.IOUtils.TPath.GetLibraryPath);Result := AResultStr ;
end;
2.2、服务端:
/// <summary>将(大文件拆分后的)单个小临时文件上传到服务器的函数;///参数为文件数据流和服务器端应用库路径下的子路径 </summary>
function TServerMethods1.upLoadFileFromTStreamBufferRead(const ATStream:TStream; const toFilePath:string): Boolean;
const BufSize = $F000; //:默认$F000:61440:60kb的小文件 //System.Classes.fmCreate
var Buffer: TBytes; ReadCount,times: Integer; FS: TFileStream;
beginif FileExists(toFilePath)=true thenbeginTThread.Synchronize(nil,procedurebeginTFile.Delete(toFilePath);MainServerForm.Memo_Errors.Lines.Add('删除文件:'+toFilePath.Trim);end);end;//:存在就先删除//删除后然后产生新文件:FS := TFileStream.Create(toFilePath, System.Classes.fmCreate); //:常量默认fmCreate = $FF00; //: TFileStream create modeTThread.Synchronize(nil,procedurebegin//测试://MainServerForm.Memo_Errors.Lines.Add('新产生文件:'+toFilePath.Trim);end);try//if ATStream.Size = -1 then//:大小未知则一直读取到没有数据为止:beginSetLength( Buffer, BufSize );repeatReadCount := ATStream.Read( Buffer[0], BufSize );if ReadCount > 0 thenFS.WriteBuffer( Buffer[0], ReadCount );if ReadCount < BufSize thenbreak;until ReadCount < BufSize;end //else FS.CopyFrom(ATStream, 0); //:大小已知则直接复制数据finallyFS.Free;Result := True;end;
end;/// <summary>多文件流合并(用文件流方法实例化抽象流):</summary>
function TServerMethods1.mergeFilesUseTFileStream(const toFilePath:string): Boolean;
var filesCount :Integer; SearchRec :TSearchRec; currPath :string;fileName :string; filesName :TStringList; FileTStream,tmpTStream :TStream;
beginTDirectory.SetCurrentDirectory(System.IOUtils.TPath.GetLibraryPath);fileName:=System.IOUtils.TPath.GetLibraryPath + toFilePath;if TDirectory.Exists(TDirectory.GetParent(fileName)) thenbegincurrPath:=TDirectory.GetParent(fileName);//强制路径产生://ForceDirectories(currPath) //DirectoryExists:路径是否存在//FileExists:文件是否存在:uses System.SysUtils;TDirectory.SetCurrentDirectory(currPath);//:进入该路径(设置当前路径并进入)if FileExists(fileName)=true then TFile.Delete(fileName);end;filesCount := FindFirst(currPath + '\' + '*.tmp', faAnyFile, SearchRec);//:搜索成功返回0if filesCount<>0 thenbeginSystem.SysUtils.FindClose(SearchRec);//?不识别:需要带上前缀:System.SysUtils.TDirectory.SetCurrentDirectory(System.IOUtils.TPath.GetLibraryPath);Result :=false; exit;end; FileTStream:=TFileStream.Create(fileName,fmCreate);filesName:=TStringList.Create;while filesCount = 0 dobeginfilesName.Add(SearchRec.Name);tmpTStream:=TFileStream.Create(SearchRec.Name,fmOpenRead);try//Vcl.Forms.Application.ProcessMessages;//:不卡界面:VCL将线程池TServerMethods1的消息事件交给应用的主线程//:不卡界面:FMX用线程同步方法TThread.SynchronizeFileTStream.CopyFrom(tmpTStream,0);filesCount := FindNext(SearchRec);finallytmpTStream.disposeOf; end;end;tryfor filesCount := 0 to filesName.Count-1 dobeginif FileExists(filesName[filesCount].Trim)=true thenTFile.Delete(filesName[filesCount].Trim);end;finallySystem.SysUtils.FindClose(SearchRec);//?不识别:需要带上前缀:System.SysUtils.filesName.disposeOf; FileTStream.disposeOf; TDirectory.SetCurrentDirectory(System.IOUtils.TPath.GetLibraryPath);end;Result :=true;
end;
并行上传,CPU 8路全开,几乎全利用率满负荷上传。
本博客关联文章:源码下载:https://download.csdn.net/download/pulledup/12578989
《TTreeView完整的枚举和递归算法》 https://blog.csdn.net/pulledup/article/details/103687816
喜欢的话,就在下面点个赞、收藏就好了,方便看下次的分享: