文章目录
- 从上次断开位置继续下载
- 实现原理(客户端):
- HTTP实现(流程):
- 图1: 客户端 Range头部信息
- 图2:服务端Content-Range信息
- 客户端与服务端的请求与响应图解:
- 获取文件 ETag 的函数:
- 下载文件(断点续传) 的函数:
从上次断开位置继续下载
HTTP断点续传:
实现原理(客户端):
1.客户端保存当前已下载文件的位置或大小。
2.在下次请求时告诉服务端自己请求的是哪一区间的数据。
3.服务端根据请求中的范围,从文件指定位置取出区间范围的数据进行响应。
关键问题:上次下载文件跟本次断点续传请求文件不一致(上次断开后,文件数据发生改变)
一旦上次断开后文件数据发生了改变,就算客户端请求的是区间数据,也为了防止数据不一致重新返回完整的文件数据
HTTP实现(流程):
1.服务端对客户的响应中加: Accept-Ranges:bytes 表示自己支持断点续传。同时需要在响应中加入: ETag:“文件唯一标识符” (判断文件是否发生变化)2.客户端在断点续传时,通过字段if-Range:Etag的值,告诉服务端文件是否发生改变,如果没改变则断点续传,改变则下载完整。客户端在断点续传时,通过字段Range:bytes=200-1000,2000-3423,5000- 告诉服务器请求那部分数据。如图1:3.服务器根据断点续传中的if-Rang 和 Range 字段确定是否可以断点续传,如果可以
(1)文件没有改变,可以续传,则响应206表示部分传输,并且正文是部分文件数据
(2)文件改变,不能续传,则响应200表示这是新的下载,下载全部文件数据 并且服务器响应中,携带字段Content-Range:bytes 200-1000/78978 表示当前响应数据是那一部分数据(语法如图2)
图1: 客户端 Range头部信息
图2:服务端Content-Range信息
客户端与服务端的请求与响应图解:
获取文件 ETag 的函数:
GetIndentifer 函数作用:利用文件的**大小**和**文件最后一次修改时间**拼接起来返回拼接字符串;作为 ETag
static std::string GetIndentifer(const std::string& path) { uint64_t mtime, fsize; fsize = fs::file_size(path); auto time_type = fs::last_write_time(path); mtime = decltype(time_type)::clock::to_time_t(time_type); std::stringstream ss; ss << fsize << mtime ; std::cout<<ss.str()<<"\n"; return ss.str();
}
下载文件(断点续传) 的函数:
Download 函数的作用(取Download中的断点续传部分):下载函数在页面展示之后,可以通过页面中文件显示点击下载;1.通过请求方法获取到文件名称;2.添加文件路径+文件名;3.调用GetIndentifer函数获取文件的ETag4.判断请求方法中是否有If-Range的头部字段;有则表示支持断点续传:5.判断请求方法中的 If-Range对应的ETag 与当前的ETag是否相同相同则表示文件没有发生改变;不相同表示文件发生改变,则从头下载;6.从请求方法获取需要续传的文件起始位置和结束位置。7.从文件中读取到相应位置的数据放入响应正文;8.设置头部信息,设置响应状态码为206 ,表示区间数据响应成功;
static void Download(const httplib::Request& req,httplib::Response& rsp){ std::cout<<"Download ......\n";std::string name=req.matches[1];std::string pathname=BACKUP_PATH+name;std::string newetag=GetIndentifer(pathname);uint64_t fsize=fs::file_size(pathname);if(req.has_header("If-Range")){std::string oldetag=req.get_header_value("If-Range");if(oldetag==newetag){//断点区间,获取数据范围std::cout<<req.ranges[0].first<<" - "<<req.ranges[0].second<<"\n";int64_t start=req.ranges[0].first;int64_t end=req.ranges[0].second;Util::RangeRead(pathname,&rsp.body,&start,&end);rsp.set_header("Content-Type","application/octet-stream");rsp.set_header("ETag",newetag);std::stringstream ss;ss<<"bytes "<<start<<"-"<<end<<"/"<<fsize;// /后面跟文件大小,不知道大小可以写 * std::cout<<ss.str()<<std::endl;rsp.set_header("Content-Range",ss.str());rsp.status=206;return;}}
}