下载大文件时,断点续传是很有必要的,特别是网速度慢且不稳定的情况下,很难保证不出意外,一旦意外中断,又要从头下载,会很让人抓狂。断点续传就能很好解决意外中断情况,再次下载时不需要从头下载,从上次中断处继续下载即可,这样下载几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打包项目以及静态网站部署项目到阿里云ECS云服务器初学简单教程

    准备工作: 1.首先进入https://ecs.console.aliyun.com/ 领取或者购买一台简单的ECS云服务器. 进入网站注册登录后拉到页面最下面或者顶部搜索免费云服务器领取立即试用 , ...

  2. 2023-03-07:x264的视频编码器,不用ffmpeg,用libx264.dll也行。请用go语言调用libx264.dll,将yuv文件编码成h264文件。

    2023-03-07:x264的视频编码器,不用ffmpeg,用libx264.dll也行.请用go语言调用libx264.dll,将yuv文件编码成h264文件. 答案2023-03-07: 使用 ...

  3. 2022-11-01:给定一个只由小写字母和数字字符组成的字符串str。 要求子串必须只含有一个小写字母,数字字符数量随意。 求这样的子串最大长度是多少?

    2022-11-01:给定一个只由小写字母和数字字符组成的字符串str. 要求子串必须只含有一个小写字母,数字字符数量随意. 求这样的子串最大长度是多少? 答案2022-11-01: 经典的滑动窗口问 ...

  4. vb.net 数据库连接字符串

    '设置数据库连接字符串 Dim connString As String = "Data Source=.\SQLEXPRESS;Initial Catalog=YourDatabaseNa ...

  5. 2020-06-11-ASP.NET Core Blazor 子组件父组件数据同步的问题

    上一篇写数据绑定的文章,写到最后留了一个坑.当子组件绑定父组件的一个字段,并且子组件修改它的时候父组件不能实时进行同步更新UI的问题,最近终于在Blazui作者的指导下搞定了. UserInfo类要实 ...

  6. ES 数据没了?谁动了我的数据?

    背景 我们在使用 Elasticsearch 的时候,可能会遇到数据"丢"了的情况.有可能是数据没成功写入 ES 集群,也可能是数据被误删了. 针对数据被误删,有没有好的解决办法呢 ...

  7. 从源码角度剖析 golang 如何fork一个进程

    从源码角度剖析 golang 如何fork一个进程 创建一个新进程分为两个步骤,一个是fork系统调用,一个是execve 系统调用,fork调用会复用父进程的堆栈,而execve直接覆盖当前进程的堆 ...

  8. 使用 conda 和 Jupyter 创建你的自定义 R 包,转换笔记为幻灯片

    创建你的自定义 R 包 出于用户使用方便考虑,Anaconda 已经在 "R Essentials" 中打包了一些最常用的数据科学 R 包.使用 conda metapackage ...

  9. 手撕HashMap(二)

    这里再补充几个手撕HashMap的方法 1.remove() remove 方法参数值应该是键值对的键的值,当传入键值对的键的时候,remove 方法会删除对应的键值对 需要利用我们自己先前创建的 h ...

  10. 最近很火的开源培训系统,支持免费商用,3个月1000star!

    项目简介 PlayEdu 开源培训系统自发布以来,3个月内帮助上千位开发者部署了私有化培训平台,并在 Github 上获得了1000star. 项目地址 Github 地址:https://githu ...