lazarus、delphi文件Http下载断点续传的实现
下载大文件时,断点续传是很有必要的,特别是网速度慢且不稳定的情况下,很难保证不出意外,一旦意外中断,又要从头下载,会很让人抓狂。断点续传就能很好解决意外中断情况,再次下载时不需要从头下载,从上次中断处继续下载即可,这样下载几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文件Http下载断点续传的实现的更多相关文章
- 金山云 KS3 Python SDK 多线程并发上传文件;下载断点续传 参考脚本
并发上传 基于py自带模块 concurrent.futures import ThreadPoolExecutor #!/usr/bin/env python3 # -*- coding:utf-8 ...
- Delphi从Internet下载文件
Delphi从Internet下载文件 今天在做拍卖系统的时候,因考虑到网络状况问题,需要将拍品所有信息下载到本机,包括拍品图片,因此需要实现从Internet下载文件的功能. 下面是代 ...
- Delphi阿里云对象存储OSS【支持上传文件、下载文件、删除文件、创建目录、删除目录、Bucket操作等】
作者QQ:(648437169) 点击下载➨Delphi阿里云对象存储OSS 阿里云api文档 [Delphi阿里云对象存储OSS]支持 获取Bucket列表.设置Bucket ...
- [No00006B]方便的网络下载工具wget 可下载网站目录下的所有文件(可下载整个网站)
wget是linux下命令行的下载工具,功能很强大,它能完成某些下载软件所不能做的,比如如果你想下载一个网页目录下的所有文件,如何做呢?网络用户有时候会遇到需要下载一批文件的情况,有时甚至需要把整个网 ...
- .net 实现上传文件分割,断点续传上传文件
一 介绍 断点续传搜索大部分都是下载的断点续传,涉及到HTTP协议1.1的Range和Content-Range头. 来个简单的介绍 所谓断点续传,也就是要从文件已经下载的地方开始继续下载.在以前版本 ...
- android 多线程下载 断点续传
来源:网易云课堂Android极客班第八次作业练习 练习内容: 多线程 asyncTask handler 多线程下载的原理 首先获取到目标文件的大小,然后在磁盘上申请一块空间用于保存目标文件,接着把 ...
- Asp.net mvc 大文件上传 断点续传
Asp.net mvc 大文件上传 断点续传 进度条 概述 项目中需要一个上传200M-500M的文件大小的功能,需要断点续传.上传性能稳定.突破asp.net上传限制.一开始看到51CTO上的这 ...
- 使用NSURLConnection实现大文件断点下载
使用NSURLConnection实现大文件断点下载 由于是实现大文件的断点下载,不是下载一般图片什么的.在设计这个类的时候本身就不会考虑把下载的文件缓存到内存中,而是直接写到文件系统. 要实现断点下 ...
- Android使用OKHttp3实现下载(断点续传、显示运行进度)
OKHttp3是现在很流行的Android网络请求框架,那么怎样利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思 准备阶段 我们会用到OKHttp3来做网络请求,使用R ...
- 一个C#文件传输模块,支持断点续传
一个C#文件传输模块,支持断点续传 最近做一个程序需要传送文件,在网上找了好久也没找到好用的方案,于是自己写了一个,与大家分享,希望大家帮忙改进,拍砖欢迎-文件采取分块发送,每块单独校验,能够保证文件 ...
随机推荐
- Grafana系列-统一展示-7-ElasticSearch数据源
系列文章 Grafana 系列文章 ElasticSearch 数据源 Grafana内置了对Elasticsearch的支持.你可以进行多种类型的查询,以可视化存储在Elasticsearch中的日 ...
- Solon 用 throw 抛出数据
此文主要是想在观念上有所拓展.在日常的接口开发时,数据的输出可以有两种方式: 返回(常见) 抛出(可以理解为越级的.越类型的返回) 我们经常会看到类似这样的案例.为了同时支持正常的数据和错误状态,选择 ...
- Error in render: “TypeError: Cannot read property ‘0‘ of null“
我们web的同学运行程序时经常会遇到如下错误,而查找起来却相当费劲 看错误提示第一反应会想到是不是我的js 方法中的某个对象取值错误了,如: 但完全错了,当你把方法里的js 翻来覆去找了一遍又一遍,任 ...
- Linux,会这些就够了
在测试当中,其实对Linux的要求不高,我们在工作中需要记住常用的一些命令,不常用的实际用到的时候再查在记即可,最重要我们要使用命令可以查看日志,定位bug 目录篇: 可用 pwd 命令查看用 ...
- Hugging News #0526: Hugging Cast 发布第一期、邀请来认领自己的论文啦!
每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...
- centos安装Vue
一直以来,有关LINUX的系统安装包,都是比较随意,直接使用yum进行或者apt-get 安装 标准安装流程是什么的呢.我们通过centos安装Vue进行展示 1 首先下载安装nodejs , htt ...
- Not a managed type: class com.example.commonspojo.entity,公共实体类剥离,然后引入报错的问题及解决办法
最近搞springcloud项目遇到在商品服务中调用基本服务时jvm扫描不到的问题 需要加@entityscan 学习博客: (9条消息) Not a managed type: class com. ...
- kprobe_events shell模式使用教程
kprobe_events shell模式使用教程 kprobe 使用前提 需要内核启用以下配置 CONFIG_KPROBES=y CONFIG_HAVE_KPROBES=y CONFIG_KPROB ...
- 7-9 《Fibonacci 数列》
7-9 <Fibonacci 数列> 思路:吃过前面<序列求和>问题的亏 但还是要尝试一下循环大法 Fn=Fn-1+Fn-2 定义int型 aFn-1 , bFn-2 , cF ...
- 拥抱jsx,开启vue3用法的另一种选择🔥🔥
背景 公司高级表单组件ProForm高阶组件都建立在jsx的运用配置上,项目在实践落地过程中积累了丰富的经验,也充分感受到了jsx语法的灵活便捷和可维护性强大,享受到了用其开发的乐趣,独乐乐不如众乐乐 ...