IOS Http斷點續傳淺析

來源:互聯網
上載者:User

標籤:

下載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斷點續傳淺析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.