下载大文件时,断点续传是很有必要的,特别是网速度慢且不稳定的情况下,很难保证不出意外,一旦意外中断,又要从头下载,会很让人抓狂。断点续传就能很好解决意外中断情况,再次下载时不需要从头下载,从上次中断处继续下载即可,这样下载几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. 2020-10-30:给定一个正数数组arr(即数组元素全是正数),找出该数组中,两个元素相减的最大值,其中被减数的下标不小于减数的下标。即求出: maxValue = max{arr[j]-arr[i] and j >= i}?

    福哥答案2020-10-30:1.双重遍历法.2.一次遍历法.golang代码如下: package main import "fmt" const INT_MAX = int(^ ...

  2. 2022-01-09:整数转换英文表示。将非负整数 num 转换为其对应的英文表示。 示例 1: 输入:num = 123, 输出:“One Hundred Twenty Three“。 力扣273。

    2022-01-09:整数转换英文表示.将非负整数 num 转换为其对应的英文表示. 示例 1: 输入:num = 123, 输出:"One Hundred Twenty Three&quo ...

  3. Vue根据时间戳制作倒计时15分钟

    废话不多说直接上代码 <script> export default { data() { return { downTimeShow: true, timer: null, downTi ...

  4. 大三ACM第一次开会

    现在是2020.9.12,单说时间的话可能感知不太强,那么换个时间, 现在是大三上.按照设想,我应该已经退役. 会上,老李的语气不再激昂,满含着无奈与沧桑.面对围在桌前的大三们,终究还是提出了那个问题 ...

  5. 如何将jq动画做出高帧的感觉?(丝滑顺畅)

    前言 我最近在一点一点研究我 博客园 的前端代码,算是边敲边学吧,还算是挺有意思的. 是这样的,之前见过一个效果,就是先显示博客的背景,然后博客主界面缓缓的上升到正确位置,于是乎,干他!开撸代码! 各 ...

  6. Vue 路由router

    简单案例: App.vue是核心组件,其中的<router-link>相当于a标签,to相当于href,export是暴露函数,这样某组件才能被其他组件识别到 代码: <templa ...

  7. nginx发布vue 项目

    在本次使用nginx发布vue项目遇到 配置location 始终404 和 在项目子目录点击浏览器刷新出现404问题 使用nginx发布vue项目,为了方便测试就下载了一个nginx 放置自己目录下 ...

  8. 代码随想录算法训练营Day15 二叉树| 层序遍历 10 226.翻转二叉树 101.对称二叉树 2

    代码随想录算法训练营 代码随想录算法训练营Day15 二叉树| 层序遍历 10 226.翻转二叉树 101.对称二叉树 2 层序遍历10 题目链接:层序遍历10 给你二叉树的根节点 root ,返回其 ...

  9. 一分钟学一个 Linux 命令 - mkdir 和 touch

    前言 大家好,我是god23bin.欢迎来到<一分钟学一个 Linux 命令>系列,今天需要你花两分钟时间来学习下,因为今天要讲的是两个命令,mkdir 和 touch 命令.前一个命令是 ...

  10. 【Python爬虫】批量爬取网页的图片&制作数据集

            由于Python拥有强大且丰富的类库,语法简单,效率高而被广泛运用于网络爬虫,很多人都是通过爬虫认识Python.         因为小编最近正在做目标识别相关的项目,所以需要大量的 ...