delphi分块压缩使用http协议base64上传下载超大文件

目录

delphi分块压缩使用http协议base64上传下载超大文件

一、知识点:

二、需要注意的事项

2.1、优化上传下载的速度与并发性能的综合考虑

2.2、服务端http协议ContentType(mime-type)相关列表类型的注册

2.3、Buffer.size对Base64分块断点续传的影响

2.4、为何需使用TFileStream文件流替代内存流TMemoryStream

三、客户端代码

四、服务端代码


一、知识点:

1、Delphi自带的压缩解压单元system.zlib.pas中核心函数的使用

2、服务端http协议ContentType(mime-type)相关列表类型的注册

3、Base64编码的规则

4、为何要分块断点续传,并使用TFileStream文件流替代内存流TMemoryStream

5、Buffer.size对Base64分块断点续传的影响

6、优化上传下载的速度与并发性能的综合考虑

二、需要注意的事项

2.1、优化上传下载的速度与并发性能的综合考虑

2.2、服务端http协议ContentType(mime-type)相关列表类型的注册

2.3、Buffer.size对Base64分块断点续传的影响

         为何使用Base64?

        如果你仅仅是上传下载,而无需下载后H5加载,可以不必非得使用TBase64Encoding来编解码。

        否则,请使用Base64,它可以对html和URL进行编解码。可对相关代码进行客制化。

         Base64内容传输时需要注意的事项:

http分块上传或下载时,需注意,假如:每次拷贝接近1M:
            block := 6*(25*7)* 1024 * 1 =  8* 128 * 1024 * 1 ;//=1050KB
            提升服务器并发性能:拷贝分块大小,系统默认默认32kb
            一次上传,最多不能超过25M,似乎超过了,就没有响应
            Buffer不正确,分片时,会对Base64产生无规律的不可预期的影响,因为填充会出问题;块:6位字节的整数倍;
            系统默认buffer.size=32k,太小了:
            1.1、使用者会感觉太慢了
            1.2、某些服务器也可能做了限制:不允许连续发小包给它,它人为你是在http攻击
                   buffer.size=N个KB,太大了:
                   1.2.1、使用者内存不允许:上限 65535KB; 总之最好不要超过1M
                   1.2.2、服务器并发时,
                           内存(取决于服务器内存的大小)
                           磁盘(取决服务器硬盘通道即单位时间IO速度)、
                           网路带宽(取决你服务器的带宽)
                           它们受不了大的“冲击波"

​
        Base64 内容传输的W3C标准说明,请自行查阅互联网标准组织文档,rfc2045。

2.4、为何需使用TFileStream文件流替代内存流TMemoryStream

        并发时,压缩解压也好、上传下载也好,或使用内存流TMemoryStream,内存的开销太大、而且内存很昂贵,使用文件流TFileStream替代内存流,会有效避免此问题。

        用Delphi system.zlib.pas库单元函数压缩解压时,要特别注意:

        2.4.1、不要期待你能用常用的压缩解压工具,去打开system.zlib压缩文件,因为它是Delphi专用的压缩格式,加了密的;不过这样也很安全;

        2.4.2、无论压缩环节还是解压环节均不要TStream.CopyFrom

        因为这样,会丢失字节。而应当老实的用字节数组,逐个字节的读取或写入。

三、客户端代码

  ///<summary>RestFull上传文件函数(文件流方法):
    ///上传单个文件(TFileStream实例化抽象流:文件分段传参给服务器):
  ///</summary>
function CopyFileTFileStream(const Path: string;
    const BufSize :Integer= 6*(25*7)* 1024 * 1; //:BufSize大小默认值1050KB:为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;
begin
  if (Path.Trim='') 
    or (ServerPath.Trim='') 
    or (ServerFileName.Trim='') then
  begin
    AResultStr:='文件不能为空!';
    Result := AResultStr ;  
    exit;  
  end;
  if FileExists(Path) =true then
  begin
    AResultStr:='文件存在!';
    //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*  6*(25*7)* 1024 * 1); //:BufSize大小默认值1050KB:为1个分段) ); //:如果设置的较大:超内存需要控制,单独写例程
        //$F000;//$F000:61440:60kb//:System.Classes.fmCreate
        //:印象中好像最大缓冲区就这个大小,你自己核实吧,5兆我够了
      FileBufferCount:=0;  FileSize:=BufSize;
      AResultStr:=''; 
 
      for FileBufferCount := 1 to iCircle do
      begin  //文件流无需SetSize:
        //ATStream:将大文件流AWriteStream拆分成:
          //三位数的小临时文件.tmp的文件流:
        ACopyStream:= TMemoryStream.Create;
        ATStream:=TFileStream.Create( Path
          +FillBeforeString(IntToStr(FileBufferCount),10,'0')+'.tmp'
          ,fmCreate or fmOpenReadWrite);//:FillBeforeString:自写函数,字符串末尾向左补齐字符
          //:文件流,没有就产生、有就打开 //10位:足够1T的文件拆分
        try
          if FileSize < AWriteStream.Size then FileSize:= BufSize;
          if FileSize >= AWriteStream.Size then
            FileSize:=AWriteStream.Size - (iCircle-1) * BufSize;
          ReadCount := AWriteStream.Read( Buffer[0], FileSize );
 
          AResultStr:=AResultStr+','+IntToStr(FileSize); 
 
          if ReadCount > 0 then
            ATStream.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);//:需要的场景才这样做
          except
            AWriteStream.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 then
      begin 
        try 
          ifResult:=ServerMethods
           .mergeFilesUseTFileStream(ServerPath + ServerFileName);
          //while ifResult <>true do sleep(0);//:服务器方法成功执行需要时间,需要的场景才做
        except
          AWriteStream.disposeOf; ATStream.disposeOf; ACopyStream.disposeOf; 
          Result := '文件流合并错误'; exit;
        end;
        AResultStr:=  '文件上传成功';
        AWriteStream.disposeOf;
      end;
    end else
    begin
      //小文件:已知小于最小设定值,则直接复制数据:
      ACopyStream:= TMemoryStream.Create;
      ACopyStream.CopyFrom(AWriteStream,0);
      try  
        ifResult:=ServerMethods
         .upLoadFileFromTStreamBufferRead( ACopyStream,(ServerPath+ServerFileName) );
        //:服务器方法成功执行需要时间
        //while ifResult<>true do sleep(0);//:需要的场景才这样做
        AResultStr:=IntToStr(AWriteStream.Size);
      except
        AWriteStream.disposeOf; ACopyStream.disposeOf; 
        Result := '服务器出错了'; exit;
      end;
      if ifResult=true then
      begin
        AResultStr:= '文件上传成功';
        AWriteStream.disposeOf; 
      end;
    end;
 
  end else AResultStr:='文件不存在!';
 
  System.IOUtils.TDirectory.SetCurrentDirectory
    (System.IOUtils.TPath.GetLibraryPath);
 
  Result := AResultStr ;
end;
 

四、服务端代码

/// <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;
begin
  if FileExists(toFilePath)=true then
  begin
    TThread.Synchronize(nil,
    procedure
    begin
      TFile.Delete(toFilePath);
      MainServerForm.Memo_Errors.Lines.Add('删除文件:'+toFilePath.Trim);
    end
    );
  end;//:存在就先删除
  //删除后然后产生新文件:
  FS := TFileStream.Create(toFilePath, System.Classes.fmCreate); 
    //:常量默认fmCreate = $FF00; //: TFileStream create mode
  TThread.Synchronize(nil,
  procedure
  begin//测试:
    //MainServerForm.Memo_Errors.Lines.Add('新产生文件:'+toFilePath.Trim);
  end
  );
 
  try
    //if ATStream.Size = -1 then//:大小未知则一直读取到没有数据为止:
    begin
      SetLength( Buffer, BufSize );
      repeat
        ReadCount := ATStream.Read( Buffer[0], BufSize );
        if ReadCount > 0 then
          FS.WriteBuffer( Buffer[0], ReadCount );
        if ReadCount < BufSize then
          break;
      until ReadCount < BufSize;
    end //else FS.CopyFrom(ATStream, 0)
    ; //:大小已知则直接复制数据
  finally
    FS.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;
begin
  TDirectory.SetCurrentDirectory(System.IOUtils.TPath.GetLibraryPath);
  fileName:=System.IOUtils.TPath.GetLibraryPath + toFilePath;
  if TDirectory.Exists(TDirectory.GetParent(fileName)) then
  begin
    currPath:=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);//:搜索成功返回0
  if filesCount<>0 then
  begin
    System.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 do
  begin
    filesName.Add(SearchRec.Name);
    tmpTStream:=TFileStream.Create(SearchRec.Name,fmOpenRead);
    try
      //Vcl.Forms.Application.ProcessMessages;
        //:不卡界面:VCL将线程池TServerMethods1的消息事件交给应用的主线程
        //:不卡界面:FMX用线程同步方法TThread.Synchronize
      FileTStream.CopyFrom(tmpTStream,0);
      filesCount := FindNext(SearchRec);
    finally
      tmpTStream.disposeOf;  
    end;
  end;
  try
    for filesCount := 0 to filesName.Count-1 do
    begin
      if FileExists(filesName[filesCount].Trim)=true then
        TFile.Delete(filesName[filesCount].Trim);
    end;
  finally
    System.SysUtils.FindClose(SearchRec);//?不识别:需要带上前缀:System.SysUtils.
    filesName.disposeOf;  FileTStream.disposeOf; 
    TDirectory.SetCurrentDirectory(System.IOUtils.TPath.GetLibraryPath);
  end;
 
  Result :=true;
end;

cpu 8路全开,同时并发: 

喜欢的,就收藏并点个赞,鼓励我继续技术的原创写作及经验分享。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐