zoukankan      html  css  js  c++  java
  • iOS-Http断点续传

    下载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];
        }
    }

  • 相关阅读:
    redis在java项目中的使用
    Nginx+Tomcat搭建高性能负载均衡集群
    Redis 数据类型
    MySQL 索引概述
    Spring boot 中的WebMvcConfigurerAdapter、WebMvcConfigurationSupport与WebMvcConfigurer区别
    DAO与DTO名词解释
    FindBugs-IDEA插件的使用
    Map 中有 HashMap、TreeMap、HashTable、LinkedHashMap,首先简单说一下他们之间的区别:
    javax.el.PropertyNotFoundException:
    内省(introspector)------>JavaBean
  • 原文地址:https://www.cnblogs.com/linxiu-0925/p/5224805.html
Copyright © 2011-2022 走看看