你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS編程技術 >> IOS Http斷點續傳淺析

IOS Http斷點續傳淺析

編輯:IOS編程技術

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

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved