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#文件传输模块,支持断点续传 最近做一个程序需要传送文件,在网上找了好久也没找到好用的方案,于是自己写了一个,与大家分享,希望大家帮忙改进,拍砖欢迎-文件采取分块发送,每块单独校验,能够保证文件 ...
随机推荐
- Selenium - 元素定位(2) - XPATH进阶
Selenium - 元素定位 XPATH 定位进阶 元素示例 属性定位 # xpath 通过id属性定位 driver.find_element_by_xpath("//*[@id='kw ...
- AccessToken、for_user、get_token
在Django REST framework的SimpleJWT库中,AccessToken是一个类,用于表示一个JSON Web Token (JWT)中的访问令牌部分.访问令牌是一种常见的身份验证 ...
- Java商城网站系统设计与实现(带源码)
基于Java的商城网站系统设计与实现 功能介绍 平台采用B/S结构,后端采用主流的Springboot框架进行开发,前端采用主流的Vue.js进行开发. 整个平台包括前台和后台两个部分. 前台功能包括 ...
- Docker安装Airflow
环境 系统:Ubuntu 22.04.2 LTS docker Version:20.10.21 docker-compose version 1.29.2, python3 --version Py ...
- 2023-05-18:有 n 名工人。 给定两个数组 quality 和 wage , 其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] 。 现在我们想雇佣
2023-05-18:有 n 名工人. 给定两个数组 quality 和 wage , 其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] . 现在我们想雇佣 ...
- L2-3 智能护理中心统计
题目描述: 智能护理中心系统将辖下的护理点分属若干个大区,例如华东区.华北区等:每个大区又分若干个省来进行管理:省又分市,等等.我们将所有这些有管理或护理功能的单位称为"管理结点" ...
- 把vue组件发布到npm
一直以来项目都使用他人开发的组件,于是乎自己也想倒腾着做一个,发布到npm 在其他项目里直接使用,这个组件上传和纯js 还是有一定区别的,在这个过程中也遇到了一些小问题,网上找了许多案例,都不是太全面 ...
- 在vue 项目中嵌入jsp页面
今日一个项目中一块功能模块是其他系统使用jsp已经开发好的页面,想着直接将其嵌入到当前的vue项目中节约开发成本:但是发现并非想象的那么简单 创建一个server.vue组件加载jsp页面 1 .第一 ...
- Anaconda入门使用指南(一)
python 是目前最流程的编程语言之一,但对于很多初学者而言,python 的包.环境以及版本的管理却是一个令人头疼的问题,特别是对于使用 Windows 的童鞋.为了解决这些问题,有不少发行版的 ...
- Android APK 文件结构
序言 APK(全称:Android application package,Android应用程序包)是Android操作系统使用的一种应用程序包文件格式,用于分发和安装移动应用及中间件. APK 文 ...