下载大文件时,断点续传是很有必要的,特别是网速度慢且不稳定的情况下,很难保证不出意外,一旦意外中断,又要从头下载,会很让人抓狂。断点续传就能很好解决意外中断情况,再次下载时不需要从头下载,从上次中断处继续下载即可,这样下载几G或十几G大小的一个文件都没问题。本文介绍利用miniframe开源Web框架分别在lazarus、delphi下实现文件HTTP下载断点续传的功能。

本文Demo还实现了批量下载文件,同步服务器上的文件到客户端的功能。文件断点续传原理:分块下载,下载后客户端逐一合并,同时保存已下载的位置,当意外中断再次下载时从保存的位置开始下载即可。这其中还要保证,中断后再次下载时服务器上相应的文件如果更新了,还得重新下载,不然下载到的文件是错了。说明:以下代码lazarus或delphi环境下都能使用。全部源码及Demo请到miniframe开源web框架下载: https://www.wyeditor.com/miniframe/或https://github.com/dajingshan/miniframe。

服务器端代码

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

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

  1. <%@//Script头、过程和函数定义
  2. program codes;
  3. %>
  4. <%!//声明变量
  5. var
  6. i,lp: integer;
  7. FileName, RelativePath, FromPath, ErrStr: string;
  8. json: TminiJson;
  9. FS: TFileStream;
  10. function GetOneDirFileInfo(Json: TminiJson; Path: string): string;
  11. var
  12. Status: Integer;
  13. SearchRec: TSearchRec;
  14. json_sub: TminiJson;
  15. begin
  16. Path := PathWithSlash(Path);
  17. SearchRec := TSearchRec.Create;
  18. Status := FindFirst(Path + '*.*', faAnyFile, SearchRec);
  19. try
  20. while Status = 0 do
  21. begin
  22. if SearchRec.Attr and faDirectory = faDirectory then
  23. begin
  24. if (SearchRec.name <> '.') and (SearchRec.name <> '..') then
  25. GetOneDirFileInfo(Json, Path + SearchRec.Name + '\');
  26. end else
  27. begin
  28. FileName := Path + SearchRec.Name;
  29. try
  30. if FileExists(FileName) then
  31. begin
  32. json_sub := Pub.GetJson;
  33. json_sub.SO; //初始化 或 json.Init;
  34. json_sub.S['filename'] := SearchRec.name;
  35. json_sub.S['RelativePath'] := GetDeliBack(FileName, FromPath);
  36. json_sub.S['FileTime'] := FileGetFileTimeA(FileName);
  37. json_sub.I['size'] := SearchRec.Size;
  38. json.A['list'] := json_sub;
  39. end;
  40. except
  41. //print(ExceptionParam)
  42. end;//}
  43. end;
  44. Status := FindNext(SearchRec);
  45. end;
  46. finally
  47. FindClose(SearchRec);
  48. SearchRec.Free;
  49. end;//*)
  50. end;
  51. %>
  52. <%
  53. begin
  54. FromPath := 'D:\code\delphi\sign\发行文件'; //下载源目录
  55. json := Pub.GetJson; //这样创建json对象不需要自己释放,系统自动管理
  56. json.SO; //初始化 或 json.Init;
  57. // 验证是否登录代码
  58. {if not Request.IsLogin('Logined') then
  59. begin
  60. json.S['retcode'] := '300';
  61. json.S['retmsg'] := '你还没有登录(no logined)!';
  62. print(json.AsJson(true));
  63. exit;
  64. end;//}
  65. json.S['retcode'] := '200';
  66. json.S['retmsg'] := '成功!';
  67. if Request.V('opr') = '1' then
  68. begin //获取服务上指定目录的文件信息
  69. GetOneDirFileInfo(Json, FromPath);
  70. end else
  71. if Request.V('opr') = '2' then
  72. begin //下载指定文件给定大小的块
  73. FromPath := PathWithSlash(FromPath);
  74. RelativePath := Request.V('fn');
  75. FileName := FromPath + RelativePath;
  76. Fs := Pub.GetFS(FileName, fmShareDenyWrite, ErrStr);
  77. if trim(ErrStr) <> '' then
  78. begin
  79. json.S['retcode'] := '300';
  80. json.S['retmsg'] := ErrStr;
  81. print(json.AsJson(true));
  82. exit;
  83. end;
  84. Fs.Position := StrToInt(Request.V('pos'));
  85. Response.ContentStream := TMemoryStream.Create; //注意不能用 Pub.GetMs,这是因为Pub.GetMs创建的对象在动态脚本运行完就释放了
  86. Response.ContentStream.CopyFrom(Fs, StrToInt(Request.V('size')));
  87. //返回流数据
  88. Response.ContentType := 'application/octet-stream';
  89. end;
  90. print(json.AsJson(true));
  91. end;
  92. %>

客户端代码

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

  1. procedure TMainForm.UpgradeBlock_Run(var ThreadRetInfo: TThreadRetInfo);
  2. const
  3. BlockSize = 1024*1024; //1M
  4. var
  5. HTML, ToPath, RelativePath, FN, Tmp, TmpFileName, FailFiles, SuccFiles, Newfn, TmpToPath: string;
  6. Json, TmpJson: TminiJson;
  7. lp, I, Number, HadUpSize, AllSize, AllBlockCount, MySize, MyNumber: Int64;
  8. Flag: boolean;
  9. SL, SLDate, SLSize, SLTmp: TStringlist;
  10. MS: TMemoryStream;
  11. Fs: TFileStream;
  12. procedure HintMsg(Msg: string);
  13. begin
  14. FMyMsg := Msg; // '正在获取文件列表。。。';
  15. ThreadRetInfo.Self.Synchronize(ThreadRetInfo.Self, MyUpdateface); //为什么不直接用匿名,因为laz不支持
  16. end;
  17. begin
  18. ToPath := 'D:\superhtml'; //如果是当前程序更新 ExtractFilePath(ParamStr(0))
  19. ThreadRetInfo.Ok := false;
  20. HintMsg('正在获取文件列表。。。');
  21. if not HttpPost('/接口/同步文件到客户端.html?opr=1',
  22. '', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML) then exit;
  23. if Pos('{', ThreadRetInfo.HTML) <> 1 then
  24. begin
  25. ThreadRetInfo.ErrStr :='请先检查脚本源码是否配置正确!';
  26. exit;
  27. end;
  28. ToPath := Pub.PathWithSlash(ToPath);
  29. Json := TminiJson.Create;
  30. SL := TStringlist.Create;
  31. SLDate := TStringlist.Create;
  32. SLSize := TStringlist.Create;
  33. SLTmp := TStringlist.Create;
  34. try
  35. Json.LoadFromString(ThreadRetInfo.HTML);
  36. if json.S['retcode'] = '200' then
  37. begin
  38. TmpJson := json.A['list'];
  39. for lp := 0 to TmpJson.length - 1 do
  40. begin
  41. HintMsg(lp.ToString + '/' + TmpJson.length.ToString + '正在检查文件:' + RelativePath);
  42. RelativePath := TmpJson[lp].S['RelativePath'];
  43. if trim(RelativePath) = '' then Continue;
  44. Flag := FileExists(ToPath + RelativePath);
  45. if Flag then
  46. begin
  47. if (PubFile.FileGetFileTimeA(ToPath + RelativePath) = TmpJson[lp].S['FileTime']) and
  48. (PubFile.FileGetFileSize(ToPath + RelativePath) = TmpJson[lp].I['Size']) then
  49. else
  50. Flag := false;
  51. end;
  52. if not Flag then //此文件需要更新
  53. begin
  54. SL.Add(RelativePath);
  55. SLDate.Add(TmpJson[lp].S['FileTime']);
  56. SLSize.Add(TmpJson[lp].S['Size']);
  57. end;
  58. end;
  59. //开始下载
  60. FailFiles := '';
  61. SuccFiles := '';
  62. HintMsg('需要更新的文件共有' + IntToStr(SL.Count) + '个。。。');
  63. for lp := 0 to SL.Count - 1 do
  64. begin
  65. RelativePath := SL[lp];
  66. if RelativePath[1] = '\' then RelativePath := Copy(RelativePath, 2, MaxInt);
  67. FN := ToPath + RelativePath;
  68. //先计算要分几个包,以处理进度
  69. Number := 0;
  70. HadUpSize := 0;
  71. AllSize := StrToInt64(SLSize[lp]);
  72. AllBlockCount := 0;
  73. while true do
  74. begin
  75. AllBlockCount := AllBlockCount + 1;
  76. if AllSize - HadUpSize >= BlockSize then
  77. MySize := BlockSize
  78. else
  79. MySize := AllSize - HadUpSize;
  80. HadUpSize := HadUpSize + MySize;
  81. if HadUpSize >= AllSize then
  82. break;
  83. end;
  84. //开始分块下载
  85. Number := 0;
  86. HadUpSize := 0;
  87. //AllSize := Fs.Size;
  88. //TmpToPath := PubFile.FileGetTemporaryPath;
  89. Newfn := '@_' + PubPWD.GetMd5(SLDate[lp] + SLSize[lp]) + ExtractFileName(FN); //Pub.GetClientUniqueCode;
  90. if FileExists(ToPath + Newfn) and (FileExists(FN)) then
  91. begin
  92. SLTmp.LoadFromFile(ToPath + Newfn);
  93. MyNumber := StrToInt64(trim(SLTmp.Text));
  94. Fs := TFileStream.Create(FN, fmOpenWrite);
  95. end else
  96. begin
  97. MyNumber := 0;
  98. Fs := TFileStream.Create(FN, fmCreate);
  99. end;
  100. try
  101. while true do
  102. begin
  103. HintMsg('正在下载文件[' + Pub.GetDeliBack(RelativePath, '@@') + ']第[' + IntToStr(Number + 1) + '/' + IntToStr(AllBlockCount) + ']个包。。。');
  104. if AllSize - HadUpSize >= BlockSize then
  105. MySize := BlockSize
  106. else
  107. MySize := AllSize - HadUpSize;
  108. Number := Number + 1;
  109. if (MyNumber = 0) or (Number >= MyNumber) or (HadUpSize + MySize >= AllSize) then
  110. begin
  111. for I := 1 to 2 do //意外出错重试一次
  112. begin
  113. if not HttpPost('/接口/同步文件到客户端.html?opr=2fn=' + UrlEncode(RelativePath) +
  114. 'pos=' + UrlEncode(IntToStr(HadUpSize)) + 'size=' + UrlEncode(IntToStr(MySize)),
  115. '', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML, MS) then
  116. begin
  117. if I = 2 then
  118. begin
  119. ThreadRetInfo.ErrStr := Json.S['retmsg'];
  120. exit;
  121. end else
  122. Continue;
  123. end;
  124. if Pos('{', ThreadRetInfo.HTML) < 1 then
  125. begin
  126. if I = 2 then
  127. begin
  128. ThreadRetInfo.ErrStr := Json.S['retmsg'];
  129. exit;
  130. end else
  131. Continue;
  132. end;
  133. Json.LoadFromString(ThreadRetInfo.HTML);
  134. if json.S['retcode'] <> '200' then
  135. begin
  136. if I = 2 then
  137. begin
  138. ThreadRetInfo.ErrStr := Json.S['retmsg'];
  139. exit;
  140. end else
  141. Continue;
  142. end;
  143. break;
  144. end;
  145. if MS = nil then
  146. begin
  147. ThreadRetInfo.ErrStr := '没能下载到文件[' + RelativePath + ']!' + json.S['retmsg'];
  148. exit;
  149. end else
  150. begin
  151. Fs.Position := HadUpSize;
  152. MS.Position := 0;
  153. Fs.CopyFrom(MS, MS.Size);
  154. MS.Free;
  155. MS := nil;
  156. SLTmp.Text := Number.ToString;
  157. try
  158. SLTmp.SaveToFile(ToPath + Newfn);
  159. except
  160. end;
  161. end;
  162. end;
  163. HadUpSize := HadUpSize + MySize;
  164. if HadUpSize >= AllSize then
  165. begin //全部下载完成
  166. Fs.Free;
  167. Fs := nil;
  168. Sleep(10);
  169. PubFile.FileChangeFileDate(Fn, SLDate[lp]);
  170. DeleteFile(ToPath + Newfn);
  171. SuccFiles := SuccFiles + #13#10 + RelativePath;
  172. break;
  173. end;
  174. end;
  175. finally
  176. if Fs <> nil then
  177. Fs.Free;
  178. end;
  179. end;
  180. ThreadRetInfo.HTML := '';
  181. if trim(SuccFiles) <> '' then
  182. ThreadRetInfo.HTML := '本次更新了以下文件:'#13#10 + SuccFiles;
  183. //if trim(FailFiles) <> '' then
  184. //ThreadRetInfo.HTML := trim(ThreadRetInfo.HTML + #13#10'以下文件更新失败:'#13#10 + FailFiles);
  185. end;
  186. finally
  187. SLTmp.Free;
  188. SLSize.Free;
  189. SL.Free;
  190. Json.Free;
  191. SLDate.Free;
  192. end;
  193. ThreadRetInfo.Ok := true;
  194. end;

以下是Demo运行界面:

金蜘蛛网页设计器
2020-2023版权所有

 

lazarus、delphi文件Http下载断点续传的实现的更多相关文章

  1. 金山云 KS3 Python SDK 多线程并发上传文件;下载断点续传 参考脚本

    并发上传 基于py自带模块 concurrent.futures import ThreadPoolExecutor #!/usr/bin/env python3 # -*- coding:utf-8 ...

  2. Delphi从Internet下载文件

    Delphi从Internet下载文件   今天在做拍卖系统的时候,因考虑到网络状况问题,需要将拍品所有信息下载到本机,包括拍品图片,因此需要实现从Internet下载文件的功能.      下面是代 ...

  3. Delphi阿里云对象存储OSS【支持上传文件、下载文件、删除文件、创建目录、删除目录、Bucket操作等】

    作者QQ:(648437169) 点击下载➨Delphi阿里云对象存储OSS             阿里云api文档 [Delphi阿里云对象存储OSS]支持 获取Bucket列表.设置Bucket ...

  4. [No00006B]方便的网络下载工具wget 可下载网站目录下的所有文件(可下载整个网站)

    wget是linux下命令行的下载工具,功能很强大,它能完成某些下载软件所不能做的,比如如果你想下载一个网页目录下的所有文件,如何做呢?网络用户有时候会遇到需要下载一批文件的情况,有时甚至需要把整个网 ...

  5. .net 实现上传文件分割,断点续传上传文件

    一 介绍 断点续传搜索大部分都是下载的断点续传,涉及到HTTP协议1.1的Range和Content-Range头. 来个简单的介绍 所谓断点续传,也就是要从文件已经下载的地方开始继续下载.在以前版本 ...

  6. android 多线程下载 断点续传

    来源:网易云课堂Android极客班第八次作业练习 练习内容: 多线程 asyncTask handler 多线程下载的原理 首先获取到目标文件的大小,然后在磁盘上申请一块空间用于保存目标文件,接着把 ...

  7. Asp.net mvc 大文件上传 断点续传

    Asp.net mvc 大文件上传 断点续传 进度条   概述 项目中需要一个上传200M-500M的文件大小的功能,需要断点续传.上传性能稳定.突破asp.net上传限制.一开始看到51CTO上的这 ...

  8. 使用NSURLConnection实现大文件断点下载

    使用NSURLConnection实现大文件断点下载 由于是实现大文件的断点下载,不是下载一般图片什么的.在设计这个类的时候本身就不会考虑把下载的文件缓存到内存中,而是直接写到文件系统. 要实现断点下 ...

  9. Android使用OKHttp3实现下载(断点续传、显示运行进度)

    OKHttp3是现在很流行的Android网络请求框架,那么怎样利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思 准备阶段 我们会用到OKHttp3来做网络请求,使用R ...

  10. 一个C#文件传输模块,支持断点续传

    一个C#文件传输模块,支持断点续传 最近做一个程序需要传送文件,在网上找了好久也没找到好用的方案,于是自己写了一个,与大家分享,希望大家帮忙改进,拍砖欢迎-文件采取分块发送,每块单独校验,能够保证文件 ...

随机推荐

  1. vue【解决方案】页面/路由跳转后,滚动条消失,页面无法滚动

    原因解析: vue项目中,页面/路由跳转后,body 的内联样式变成 overflow:hidden 解决方案: 使用路由守卫,在页面/路由跳转后,将body 的overflow设置为auto src ...

  2. 2023-03-29:如何高效计算三条线路选择方案?小A的旅行线路规划问题

    2023-03-29:第一行有一个正整数n(3<=n<=100000),代表小A拟定的路线数量 第二行有n个正整数,第i个代表第i条路线的起始日期 第三行有n个正整数,第i个代表第i条路线 ...

  3. 2022-03-27:class AreaResource { String area; // area表示的是地区全路径,最多可能有6级,比如: 中国,四川,成都 或者 中国,浙江,杭州 Str

    2022-03-27:class AreaResource { String area; // area表示的是地区全路径,最多可能有6级,比如: 中国,四川,成都 或者 中国,浙江,杭州 Strin ...

  4. vue全家桶进阶之路4:NPM包

    NPM(Node Package Manager)是 Node.js 的包管理工具,用来安装各种 Node.js 的扩展. NPM是 JavaScript 的包管理工具,也是世界上最大的软件注册表.有 ...

  5. Blazor HyBrid 授权讲解

    Blazor HyBrid 授权讲解 本文介绍 ASP.NET Core 对 Blazor Hybrid 应用中的安全配置和管理及 ASP.NET Core Identity 的支持. Blazor ...

  6. 聊聊CSS 缓动函数的新成员linear()

    CSS 缓动函数是一种用于控制 CSS 动画过渡效果的函数,可以让动画变得更加自然.这篇文章将介绍一种新的 CSS easing function,即 linear(),它可以模拟出更复杂的缓动效果, ...

  7. Swift4 入门到精通(第二章基本数据类型与量值)

    第二章 量值和基本数据类型 Swift 支持的基本数据类型, 整型,浮点型,布尔型,元组,可选类型. 学习的目标: 常量与变量的意义.声明.命名规范.类型 数据进制与计算机存储原理 整型数据.浮点型数 ...

  8. 批量生成,本地推理,人工智能声音克隆框架PaddleSpeech本地批量克隆实践(Python3.10)

    云端炼丹固然是极好的,但不能否认的是,成本要比本地高得多,同时考虑到深度学习的训练相对于推理来说成本也更高,这主要是因为它需要大量的数据.计算资源和时间等资源,并且对超参数的调整也要求较高,更适合在云 ...

  9. 洛谷 P5540 [BalkanOI2011] timeismoney | 最小乘积生成树

    题意 给一个无向图,边有两个权 \(a\) 和 \(b\),定义一个生成树的权值是 \(\left(\sum\limits_{e\in T}a_e\right)\left(\sum\limits_{e ...

  10. ASIC加速技术原理与实践:从芯片设计到优化

    目录 <ASIC加速技术原理与实践:从芯片设计到优化> 背景介绍: 随着数字电路技术的不断发展,ASIC(专门芯片)作为数字电路中的核心部分,逐渐成为芯片设计中的重要组成部分.ASIC加速 ...