lazarus、delphi文件下载断点续传的实现
  C5GYvHGFzWMh 2023年11月02日 26 0

下载大文件时,断点续传是很有必要的,特别是网速度慢且不稳定的情况下,很难保证不出意外,一旦意外中断,又要从头下载,会很让人抓狂。断点续传就能很好解决意外中断情况,再次下载时不需要从头下载,从上次中断处继续下载即可,这样下载几G或十几G大小的一个文件都没问题。本文介绍利用miniframe开源Web框架分别在lazarus、delphi下实现文件HTTP下载断点续传的功能,本文Demo还实现了批量下载文件,同步服务器上的文件到客户端的功能。文件断点续传原理:分块下载,下载后客户端逐一合并,同时保存已下载的位置,当意外中断再次下载时从保存的位置开始下载即可。这其中还要保证,中断后再次下载时服务器上相应的文件如果更新了,还得重新下载,不然下载到的文件是错了。说明:以下代码lazarus或delphi环境下都能使用。全部源码及Demo请到miniframe开源web框架下载: https://www.wyeditor.com/miniframe/或https://github.com/dajingshan/miniframe。

服务器端代码

文件下载断点续传服务器端很简单,只要提供客户端要求下载的开始位置和指定大小的块即可。

以下是服务器获取文件信息和下载一个文件一块的代码:

<%@//Script头、过程和函数定义
 program codes;
 %>
  
 <%!//声明变量
 var
   i,lp: integer;
   FileName, RelativePath, FromPath, ErrStr: string;
   json: TminiJson;
   FS: TFileStream;
   
 function GetOneDirFileInfo(Json: TminiJson; Path: string): string;
 var
   Status: Integer;
   SearchRec: TSearchRec;
   json_sub: TminiJson;
 begin
   Path := PathWithSlash(Path);
   SearchRec := TSearchRec.Create;
   Status := FindFirst(Path + '*.*', faAnyFile, SearchRec);
   try
     while Status = 0 do
     begin 
       if SearchRec.Attr and faDirectory = faDirectory then
       begin
         if (SearchRec.name <> '.') and (SearchRec.name <> '..') then
           GetOneDirFileInfo(Json, Path + SearchRec.Name + '\');
       end else
       begin
         FileName := Path + SearchRec.Name;
         try
           if FileExists(FileName) then
           begin 
             json_sub := Pub.GetJson;  
             json_sub.SO; //初始化 或 json.Init;    
             json_sub.S['filename'] := SearchRec.name;
             json_sub.S['RelativePath'] := GetDeliBack(FileName, FromPath);
             json_sub.S['FileTime'] := FileGetFileTimeA(FileName);
             json_sub.I['size'] := SearchRec.Size;
             json.A['list'] := json_sub;
           end;
         except
           //print(ExceptionParam)
         end;//}
       end; 
       Status := FindNext(SearchRec);
     end;
   finally
     FindClose(SearchRec);
     SearchRec.Free;
   end;//*) 
 end;
 %>
 <%
 begin
   FromPath := 'D:\code\delphi\sign\发行文件'; //下载源目录
   
   json := Pub.GetJson; //这样创建json对象不需要自己释放,系统自动管理
   json.SO; //初始化 或 json.Init;
   
   // 验证是否登录代码
   {if not Request.IsLogin('Logined') then
   begin 
     json.S['retcode'] := '300';
     json.S['retmsg'] := '你还没有登录(no logined)!'; 
     print(json.AsJson(true));
     exit; 
   end;//} 
   
   json.S['retcode'] := '200';
   json.S['retmsg'] := '成功!';
   if Request.V('opr') = '1' then
   begin //获取服务上指定目录的文件信息
     GetOneDirFileInfo(Json, FromPath);
   end else
   if Request.V('opr') = '2' then
   begin //下载指定文件给定大小的块 
     FromPath := PathWithSlash(FromPath);   
     RelativePath := Request.V('fn');
     FileName := FromPath + RelativePath;
     Fs := Pub.GetFS(FileName, fmShareDenyWrite, ErrStr);
     if trim(ErrStr) <> '' then 
     begin
       json.S['retcode'] := '300';
       json.S['retmsg'] := ErrStr;
       print(json.AsJson(true));  
       exit;
     end;
     Fs.Position := StrToInt(Request.V('pos'));
     Response.ContentStream := TMemoryStream.Create; //注意不能用 Pub.GetMs,这是因为Pub.GetMs创建的对象在动态脚本运行完就释放了
     Response.ContentStream.CopyFrom(Fs, StrToInt(Request.V('size')));
     //返回流数据
     Response.ContentType := 'application/octet-stream';   
   end;
   print(json.AsJson(true));
 end;
 %>

客户端代码

客户端收到块后,进行合并。全部块下载完成后,还要把新下载的文件的文件修改为与服务器上的文件相同。以下是客户端实现的主代码:

procedure TMainForm.UpgradeBlock_Run(var ThreadRetInfo: TThreadRetInfo);
 const
   BlockSize = 1024*1024; //1M
 var
   HTML, ToPath, RelativePath, FN, Tmp, TmpFileName, FailFiles, SuccFiles, Newfn, TmpToPath: string;
   Json, TmpJson: TminiJson;
   lp, I, Number, HadUpSize, AllSize, AllBlockCount, MySize, MyNumber: Int64;
   Flag: boolean;
   SL, SLDate, SLSize, SLTmp: TStringlist;
   MS: TMemoryStream;
   Fs: TFileStream;
   procedure HintMsg(Msg: string);
   begin
     FMyMsg := Msg; // '正在获取文件列表。。。';
     ThreadRetInfo.Self.Synchronize(ThreadRetInfo.Self, MyUpdateface); //为什么不直接用匿名,因为laz不支持
   end;
 begin
   ToPath := 'D:\superhtml'; //如果是当前程序更新  ExtractFilePath(ParamStr(0))
  
   ThreadRetInfo.Ok := false;
  
   HintMsg('正在获取文件列表。。。');
   if not HttpPost('/接口/同步文件到客户端.html?opr=1',
       '', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML) then exit;
   if Pos('{', ThreadRetInfo.HTML) <> 1 then
   begin
     ThreadRetInfo.ErrStr :='请先检查脚本源码是否配置正确!';
     exit;
   end;
   ToPath := Pub.PathWithSlash(ToPath);
  
   Json := TminiJson.Create;
   SL := TStringlist.Create;
   SLDate := TStringlist.Create;
   SLSize := TStringlist.Create;
   SLTmp := TStringlist.Create;
   try
     Json.LoadFromString(ThreadRetInfo.HTML);
     if json.S['retcode'] = '200' then
     begin
       TmpJson := json.A['list'];
       for lp := 0 to TmpJson.length - 1 do
       begin
         HintMsg(lp.ToString + '/' + TmpJson.length.ToString + '正在检查文件:' + RelativePath);
         RelativePath := TmpJson[lp].S['RelativePath'];
         if trim(RelativePath) = '' then Continue;
         Flag := FileExists(ToPath + RelativePath);
         if Flag then
         begin
           if (PubFile.FileGetFileTimeA(ToPath + RelativePath) = TmpJson[lp].S['FileTime']) and
              (PubFile.FileGetFileSize(ToPath + RelativePath) = TmpJson[lp].I['Size']) then
           else
             Flag := false;
         end;
         if not Flag then //此文件需要更新
         begin
           SL.Add(RelativePath);
           SLDate.Add(TmpJson[lp].S['FileTime']);
           SLSize.Add(TmpJson[lp].S['Size']);
         end;
       end;
  
       //开始下载
       FailFiles := '';
       SuccFiles := '';
       HintMsg('需要更新的文件共有' + IntToStr(SL.Count) + '个。。。');
       for lp := 0 to SL.Count - 1 do
       begin
         RelativePath := SL[lp];
         if RelativePath[1] = '\' then RelativePath := Copy(RelativePath, 2, MaxInt);
         FN := ToPath + RelativePath;
  
         //先计算要分几个包,以处理进度
         Number := 0;
         HadUpSize := 0;
         AllSize := StrToInt64(SLSize[lp]);
         AllBlockCount := 0;
         while true do
         begin
           AllBlockCount := AllBlockCount + 1;
           if AllSize - HadUpSize >= BlockSize then
              MySize := BlockSize
           else
              MySize := AllSize - HadUpSize;
           HadUpSize := HadUpSize + MySize;
           if HadUpSize >= AllSize then
             break;
         end;
  
         //开始分块下载
         Number := 0;
         HadUpSize := 0;
         //AllSize := Fs.Size;
         //TmpToPath := PubFile.FileGetTemporaryPath;
         Newfn := '@_' + PubPWD.GetMd5(SLDate[lp] + SLSize[lp]) + ExtractFileName(FN);  //Pub.GetClientUniqueCode;
  
         if FileExists(ToPath + Newfn) and (FileExists(FN)) then
         begin
           SLTmp.LoadFromFile(ToPath + Newfn);
           MyNumber := StrToInt64(trim(SLTmp.Text));
           Fs := TFileStream.Create(FN, fmOpenWrite);
         end else
         begin
           MyNumber := 0;
           Fs := TFileStream.Create(FN, fmCreate);
         end;
         try
           while true do
           begin
             HintMsg('正在下载文件[' + Pub.GetDeliBack(RelativePath, '@@') + ']第[' + IntToStr(Number + 1) + '/' + IntToStr(AllBlockCount) + ']个包。。。');
  
             if AllSize - HadUpSize >= BlockSize then
                MySize := BlockSize
             else
                MySize := AllSize - HadUpSize;
             Number := Number + 1;
             if (MyNumber = 0) or (Number >= MyNumber) or (HadUpSize + MySize >= AllSize) then
             begin
               for I := 1 to 2 do //意外出错重试一次
               begin
                 if not HttpPost('/接口/同步文件到客户端.html?opr=2fn=' + UrlEncode(RelativePath) +
                   'pos=' + UrlEncode(IntToStr(HadUpSize)) + 'size=' + UrlEncode(IntToStr(MySize)),
                   '', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML, MS) then
                 begin
                   if I = 2 then
                   begin
                     ThreadRetInfo.ErrStr := Json.S['retmsg'];
                     exit;
                   end else
                     Continue;
                 end;
                 if Pos('{', ThreadRetInfo.HTML) < 1 then
                 begin
                   if I = 2 then
                   begin
                     ThreadRetInfo.ErrStr := Json.S['retmsg'];
                     exit;
                   end else
                     Continue;
                 end;
  
                 Json.LoadFromString(ThreadRetInfo.HTML);
                 if json.S['retcode'] <> '200' then
                 begin
                   if I = 2 then
                   begin
                     ThreadRetInfo.ErrStr := Json.S['retmsg'];
                     exit;
                   end else
                     Continue;
                 end;
                 break;
               end;
  
               if MS = nil then
               begin
                 ThreadRetInfo.ErrStr := '没能下载到文件[' + RelativePath + ']!' + json.S['retmsg'];
                 exit;
               end else
               begin
                 Fs.Position := HadUpSize;
                 MS.Position := 0;
                 Fs.CopyFrom(MS, MS.Size);
                 MS.Free;
                 MS := nil;
                 SLTmp.Text := Number.ToString;
                 try
                   SLTmp.SaveToFile(ToPath + Newfn);
                 except
                 end;
               end;
             end;
             HadUpSize := HadUpSize + MySize;
  
             if HadUpSize >= AllSize then
             begin //全部下载完成
               Fs.Free;
               Fs := nil;
               Sleep(10);
               PubFile.FileChangeFileDate(Fn, SLDate[lp]);
               DeleteFile(ToPath + Newfn);
               SuccFiles := SuccFiles + #13#10 + RelativePath;
               break;
             end;
           end;
         finally
           if Fs <> nil then
             Fs.Free;
         end;
       end;
       ThreadRetInfo.HTML := '';
       if trim(SuccFiles) <> '' then
         ThreadRetInfo.HTML := '本次更新了以下文件:'#13#10 + SuccFiles;
       //if trim(FailFiles) <> '' then
         //ThreadRetInfo.HTML := trim(ThreadRetInfo.HTML + #13#10'以下文件更新失败:'#13#10 + FailFiles);
     end;
   finally
     SLTmp.Free;
     SLSize.Free;
     SL.Free;
     Json.Free;
     SLDate.Free;
   end;
   ThreadRetInfo.Ok := true;
 end;

以下是Demo运行界面:

lazarus、delphi文件下载断点续传的实现_lazarus文件下载断点续传

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论