下载LOFTER客户端
IOS Http断点续传浅析

http实现断点续传的关键地方就是在httprequest中加入“Range”头。

//设置Range头,值:bytes=x-y;x:开始字节,y:结束字节,不指定则为文件末尾
[request addValue:@"bytes=500-" forHTTPHeaderField:@"Range"];

如果服务器正确响应的话,就可以顺利续传;如果服务器不支持,那就只能用其它方法了。

经过测试,服务器的不支持分为两种情况:

1.完全没响应

如果不处理会导致文件无法下载。

测试地址:http://dl_dir.qq.com/qqfile/qq/QQforMac/QQ_V2.4.2.dmg

发送请求后,过一段时间直接进入了didFailWithError的delegate;错误信息为time out。

针对这种情况可以做出的处理是:增加一个是否支持断点续传的标志。

具体:

第一次请求,开始字节为0,不用发送Range头,可以正常下载;

当下载中断,开始第二次请求,开始字节不为0,发送range头;

如果进入didFailWithError的delegate,就标明此链接不可以断点续传,每次请求前都清除缓存,保证开始的字节为0,不发送Range头。

2.无论发送Range的值是多少,服务器都会重新下载。

如果不处理,会导致续传过的文件出错。

测试地址:https://github.com/CocoaPods/CocoaPods/archive/master.zip

这种情况的处理方案是:

第一次收到响应的时候,就把文件的总大小记录下来;

以后每次收到响应的时候都比较一下下载长度和总大小是不是一样;

如果一样而且又存在缓存;就表明属于这种情况了;直接删掉缓存,重新下载。

下面是用NSURLConnection实现http断点续传的实例:

针对上面两种做了简单的处理,回调函数还有待添加

MXDownload.h文件:

#import <Foundation/Foundation.h>

@interface MXDownload : NSObject

//文件名路径
@property (nonatomic, readonly) NSString *filePath;

//是否正在下载的标志
@property (nonatomic, readonly) BOOL downloading;

//初始化
- (id)initWithUrlString:(NSString *)urlString;

//两个状态
- (void)start;
- (void)stop;

//清除缓存
- (void)clearCache;

@end

MXDownload.m文件:

#import "MXDownload.h"
#import "NSString+MX.h"

#define FILE_INFO_PLIST [NSString pathWithName:@"MXDownload/fileInfo.plist" directory:NSCachesDirectory]

@interface MXDownload (){
    NSURLConnection *_urlConnection;
    NSString *_urlString;
    BOOL _downloading,_didAddRange,_shouldResume;

NSString *_fileName,*_filePath, *_tempFilePath;
    NSFileHandle        *_fileHandle;
    unsigned long long  _fileOffset,_fileSize;
}

@end

@implementation MXDownload
@synthesize downloading = _downloading;
@synthesize filePath = _filePath;

//初始化,顺便设置下载文件和下载临时文件路径
- (id)initWithUrlString:(NSString *)urlString{
    self = [super init];
    if (self){
        _urlString = urlString;
        _shouldResume = YES;
        if (_urlString) {
            _fileName = [_urlString MD5];
            _filePath = [NSString pathWithName:[NSString stringWithFormat:@"MXDownload/%@",_fileName] directory:NSCachesDirectory];
            _tempFilePath = [NSString stringWithFormat:@"%@.temp",_filePath];
        }
    }
    return self;
}

//开始下载
- (void)start{
    //如果正在下载,中断
    if (_downloading) return;
    //没有url,也中断
    if (!_urlString) return;

//临时文件句柄
    _fileHandle = [NSFileHandle fileHandleForWritingAtPath:_tempFilePath];
    //获取本次请求下载开始的位置,如果文件不存在,就是0
    _fileOffset = _fileHandle ? [_fileHandle seekToEndOfFile] : 0;

//初始化请求
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:_urlString]];
    //设置缓存策略,很重要,因为文件是自己储存的,和缓存无关,所以要忽略缓存
    //要不然第二次请求会出错
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];

//最关键地方,设置Range头,值:bytes=x-y;x:开始字节,y:结束字节,不指定则为文件末尾
    _didAddRange = NO;
    if (_fileOffset != 0 && _shouldResume) {
        [request addValue:[NSString stringWithFormat:@"bytes=%llu-",_fileOffset] forHTTPHeaderField:@"Range"];
        _didAddRange = YES;
    }

_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [_urlConnection start];

_downloading = YES;
}

//结束下载
- (void)stop{
    [_urlConnection cancel];
    _urlConnection = nil;
    [_fileHandle closeFile];
    _downloading = NO;
}

//清除文件
- (void)clearCache{
    if (_downloading) [self stop];
    [[NSFileManager defaultManager] removeItemAtPath:_filePath error:nil];
    [[NSFileManager defaultManager] removeItemAtPath:_tempFilePath error:nil];
}

#pragma mark -
#pragma mark NSURLConnectionDelegate

//接收到响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    //本次请求回来的文件大小
    long long fileLength = response.expectedContentLength;
    if (fileLength == NSURLResponseUnknownLength) [self stop];

NSData *existFileData = [[NSData alloc] initWithContentsOfFile:_filePath];

//检查文件是否已下载完成
    if (existFileData && existFileData.length == fileLength) {
        NSLog(@"之前已经下载好了");
        [self stop];
    }
    else{
        //保存文件的总大小
        if (!_didAddRange){
            NSMutableDictionary *dic = [NSMutableDictionary new];
            [dic addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:FILE_INFO_PLIST]];
            [dic setValue:[NSNumber numberWithLongLong:fileLength]  forKey:_fileName];
            [dic writeToFile:FILE_INFO_PLIST atomically:YES];
        }

NSFileManager *fileManager = [NSFileManager defaultManager];
        //先清除掉旧的文件
        [fileManager removeItemAtPath:_filePath error:nil];

//如果此次请求回来的大小等于文件的总大小而且临时文件又存在,则删除临时文件
        //解决每次请求都是重新开始的问题
        NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:FILE_INFO_PLIST];
        BOOL isTotalLength = fileLength == [[dic valueForKey:_fileName] longLongValue];
        if ([fileManager fileExistsAtPath:_tempFilePath] && isTotalLength){
            [fileManager removeItemAtPath:_tempFilePath error:nil];
        }

//重新创建文件
        if (![fileManager fileExistsAtPath:_tempFilePath]){
            [fileManager createFileAtPath:_tempFilePath contents:nil attributes:nil];
            _fileHandle = [NSFileHandle fileHandleForWritingAtPath:_tempFilePath];
            _fileOffset = 0;
        }

_fileSize = fileLength + _fileOffset;

//用_fileOffset可以检查是重新下载还是继续下载
        NSLog(@"%@",_fileOffset ? @"继续下载" : @"开始下载");
    }
}

//不断接收到数据
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)aData{
    //写入文件
    [_fileHandle writeData:aData];
    _fileOffset = [_fileHandle offsetInFile];
    NSLog(@"下载进度: %lld / %lld",_fileOffset,_fileSize);
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    [self stop];
    //如果不支持续传,删掉临时文件再试一次
    if (_shouldResume) {
        _shouldResume = NO;
        [[NSFileManager defaultManager] removeItemAtPath:_tempFilePath error:nil];
        [self start];
    }
}

//完成
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
    [[NSFileManager defaultManager] moveItemAtPath:_tempFilePath toPath:_filePath error:nil];
    NSLog(@"下载完成");
    [self stop];
}

@end

调用:

- (IBAction)startDownLoad:(id)sender{
    if (_downloader == nil){
//        _downloader = [[MXDownload alloc] initWithUrlString:@"https://github.com/CocoaPods/CocoaPods/archive/master.zip"];
        _downloader = [[MXDownload alloc] initWithUrlString:@"http://dl_dir.qq.com/qqfile/qq/QQforMac/QQ_V2.4.2.dmg"];
//        _downloader = [[MXDownload alloc] initWithUrlString:@"http://192.168.50.19:8080/vcont/wb.mp3"];
    }
    if (_downloader.downloading) {
        [_downloader stop];
    }
    else{
        [_downloader start];
    }
}

iOS-Http断点续传的更多相关文章

  1. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  2. iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>

    前言:根据前篇<iOS开发之网络编程--2.NSURLSessionDownloadTask文件下载>或者<iOS开发之网络编程--3.NSURLSessionDataTask实现文 ...

  3. iOS开发之网络编程--3、NSURLSessionDataTask实现文件下载(离线断点续传下载)

    前言:使用NSURLSessionDownloadTask满足不这个需要离线断点续传的下载需求,所以这里就需要使用NSURLSessionDataTask的代理方法来处理下载大文件,并且实现离线断点续 ...

  4. iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄

    前言:本篇讲解,在前篇iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载的基础上,使用输出流代替文件句柄实现大文件断点续传.    在实际开发中,输入输出流用的比较少,但 ...

  5. iOS 应用开发中的断点续传实践总结

    断点续传概述 断点续传就是从文件上次中断的地方开始重新下载或上传数据,而不是从文件开头.(本文的断点续传仅涉及下载,上传不在讨论之内)当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者 ...

  6. iOS中的下载管理器(支持断点续传)

    在空闲时间自己编写了一个简单的iOS下载管理器.该管理器实现如下功能: 1.能够支持正常的下载,暂停,继续操作. 2.支持断点续传,实现暂停执行继续操作后,依然能正常将文件下载完成. 3.实现实时状态 ...

  7. ios 后台下载,断点续传总结

    2018年12月05日 16:09:00 weixin_34101784 阅读数:5 https://blog.csdn.net/weixin_34101784/article/details/875 ...

  8. iOS 文件下载及断点续传

    ios的下载我们可以使用的方法有:NSData.NSURLConnection.NSURLSession还有第三方框架AFNetworking和ASI 利用NSData方法和NSURLConnecti ...

  9. 数据存储之iOS断点续传

    iOS里面实现断点续传 第三方框架之AFN 代码实现 一.iOS里面实现断点续传 1⃣️AFN基于NSURL 1.性能和稳定性略差.针对JSON.XML.Plist和Image四种数据结构封装了各自处 ...

随机推荐

  1. 09—mybatis注解配置join查询

    今天来聊mybatis的join查询,怎么说呢,有的时候,join查询确实能提升查询效率,今天举个left join的例子,来看看mybatis的join查询. 就不写的很细了,把主要代码贴出来了. ...

  2. xss之上传文件的xss,绕过csp,预警机制

    xss1.XSS姿势——文件上传XSS https://wooyun.x10sec.org/static/drops/tips-14915.html总结: 1.1.文件名方式,原理:有些文件名可能反应 ...

  3. ASP.NET MVC Liu_Cabbage 个人博客

    RightControl_Blog 介绍 前台使用燕十三博客前端模板,后台基于RightControl .NET通用角色权限管理系统搭建,已完成.项目地址:http://www.baocaige.to ...

  4. docker换源

    方案一 修改或新增 /etc/docker/daemon.json # vi /etc/docker/daemon.json { "registry-mirrors": [&quo ...

  5. 2018HDU多校联赛第六场 6373 Pinball——水题&&物理题

    题意 给定一个斜面,从某处让一个小球作自由落体运动,求小球与斜面的碰撞次数(假设都为弹性碰撞). 分析 题图如下,x轴.y轴是虚拟的. 根据高中物理的套路,沿斜面方向分解重力加速度即可. #inclu ...

  6. BZOJ 1778: [Usaco2010 Hol]Dotp 驱逐猪猡 (高斯消元)

    题面 题目传送门 分析 令爆炸概率为PPP.设 f(i)=∑k=0∞pk(i)\large f(i)=\sum_{k=0}^{\infty}p_k(i)f(i)=∑k=0∞​pk​(i),pk(i)p ...

  7. [Luogu P1230]智力大冲浪

    题目链接 这道题,贪就对了. 先按照价值排序,从大到小.当前考虑的的就先放到尽可能晚的时间点,为其他的创造机会,如果这一个的所有可用时间段都被占据,就只能扣钱了. #include<fstrea ...

  8. luogu 3998 [SHOI2013]发微博 map

    考试的时候被卡常了~ code: #include <bits/stdc++.h> #define ll long long #define N 200002 #define setIO( ...

  9. adb命令积累

    1. 模拟事件全部是通过input命令来实现的,首先看一下input命令的使用: (原文:http://blog.csdn.net/huiguixian/article/details/1192538 ...

  10. 2D动画如何做出3D体积感

    https://cowlevel.net/article/1959026 <AngerForce>幕后故事 这篇文章是个老坑,最近有时间开始写,也是对之前项目的一个总结和记录吧. 本篇文章 ...