美文网首页
断点续传

断点续传

作者: 京北磊哥 | 来源:发表于2025-03-08 21:11 被阅读0次

断点续传的原理

断点续传允许在下载中断后从中断处继续下载,其核心依赖于 HTTP协议的Range请求头

  1. Range头机制:客户端通过Range: bytes=start-头告知服务器需要从哪个字节开始传输数据。
  2. 服务器响应:若服务器支持,返回206 Partial Content状态码及对应数据块;否则返回200 OK及完整数据。
  3. 本地文件管理:客户端需记录已下载大小,将新数据追加到文件末尾,确保中断后能恢复。

实现方式一:使用 NSURLSessionDownloadTask 和 ResumeData

步骤说明

  1. 创建下载任务:初始化NSURLSessionDownloadTask
  2. 暂停并保存ResumeData:调用cancelByProducingResumeData:获取恢复数据。
  3. 持久化ResumeData:将resumeData保存到磁盘或UserDefaults。
  4. 恢复下载:用resumeData重新创建任务继续下载。
  5. 完成处理:移动临时文件到目标路径。

代码示例

// 定义属性
@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
@property (nonatomic, strong) NSData *resumeData;
@property (nonatomic, strong) NSURLSession *session;

// 开始/恢复下载
- (void)startDownload {
    NSURL *url = [NSURL URLWithString:@"https://example.com/largefile.zip"];
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    
    if (self.resumeData) {
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
    } else {
        self.downloadTask = [self.session downloadTaskWithURL:url];
    }
    [self.downloadTask resume];
}

// 暂停下载
- (void)pauseDownload {
    __weak typeof(self) weakSelf = self;
    [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
        weakSelf.resumeData = resumeData;
        weakSelf.downloadTask = nil;
        // 保存到本地
        [[NSUserDefaults standardUserDefaults] setObject:resumeData forKey:@"resumeData"];
    }];
}

// 处理下载完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *destinationPath = [documentsPath stringByAppendingPathComponent:@"downloadedFile.zip"];
    
    [fileManager removeItemAtPath:destinationPath error:nil];
    [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:destinationPath] error:nil];
    
    // 清除resumeData
    self.resumeData = nil;
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"resumeData"];
}

// 错误处理
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (error) {
        // 检查是否有resumeData
        self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
        [[NSUserDefaults standardUserDefaults] setObject:self.resumeData forKey:@"resumeData"];
    }
}

注意事项

  • resumeData有效期:若URL或服务器资源变化可能导致失效。
  • 后台会话:需使用backgroundSessionConfiguration以支持后台下载。
  • 文件存储:确保临时文件未被系统清理。

实现方式二:手动处理 Range 头(使用 NSURLSessionDataTask)

步骤说明

  1. 检查本地文件:获取已下载大小。
  2. 设置Range头:构造请求时指定bytes=start-
  3. 处理服务器响应
    • 206:追加写入数据。
    • 200:重新下载(服务器不支持Range)。
  4. 实时保存进度:每次写入后更新已下载大小。
  5. 完成下载:移动文件并清理状态。

代码示例

// 定义属性
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@property (nonatomic, assign) long long currentLength;
@property (nonatomic, strong) NSFileHandle *fileHandle;
@property (nonatomic, copy) NSString *tempFilePath;

- (void)startManualResumeDownload {
    NSURL *url = [NSURL URLWithString:@"https://example.com/largefile.zip"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    // 获取已下载大小
    self.tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.zip"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:self.tempFilePath]) {
        NSDictionary *attrs = [fileManager attributesOfItemAtPath:self.tempFilePath error:nil];
        self.currentLength = [attrs fileSize];
        [request setValue:[NSString stringWithFormat:@"bytes=%lld-", self.currentLength] forHTTPHeaderField:@"Range"];
    } else {
        self.currentLength = 0;
        [fileManager createFileAtPath:self.tempFilePath contents:nil attributes:nil];
    }
    
    // 打开文件句柄
    self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.tempFilePath];
    [self.fileHandle seekToEndOfFile];
    
    // 创建任务
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
    self.dataTask = [session dataTaskWithRequest:request];
    [self.dataTask resume];
}

// 处理响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response 
 completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    if (httpResponse.statusCode == 206) {
        // 支持断点续传,继续接收
        completionHandler(NSURLSessionResponseAllow);
    } else if (httpResponse.statusCode == 200) {
        // 不支持,重置文件
        self.currentLength = 0;
        [self.fileHandle truncateFileAtOffset:0];
        completionHandler(NSURLSessionResponseAllow);
    } else {
        completionHandler(NSURLSessionResponseCancel);
    }
}

// 接收数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    [self.fileHandle writeData:data];
    self.currentLength += data.length;
    // 保存进度
    [[NSUserDefaults standardUserDefaults] setObject:@(self.currentLength) forKey:@"currentLength"];
}

// 完成或错误处理
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    [self.fileHandle closeFile];
    self.fileHandle = nil;
    
    if (!error) {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        NSString *destPath = [documentsPath stringByAppendingPathComponent:@"manualDownload.zip"];
        [fileManager moveItemAtPath:self.tempFilePath toPath:destPath error:nil];
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"currentLength"];
    }
}

注意事项

  • 服务器兼容性:确保服务器支持Range请求。
  • 进度保存:频繁保存可能导致性能问题,建议适当节流。
  • 文件完整性:下载完成后可通过MD5校验确保文件正确。

两种方式对比

特性 NSURLSessionDownloadTask 手动处理Range头
实现复杂度 简单(系统自动管理) 复杂(需手动处理Range和文件)
灵活性 低(依赖系统实现) 高(完全控制流程)
适用场景 快速实现,服务器支持标准断点续传 需深度定制或服务器特殊要求
后台下载支持 支持 需额外处理

常见问题解决

  1. resumeData无效

    • 检查URL是否变化。
    • 确保服务器资源未修改。
    • 重新下载时删除旧的resumeData。
  2. 文件损坏

    • 下载完成后校验文件哈希值。
    • 使用NSFileManager的原子写入操作。
  3. 进度不准

    • 在主线程更新UI,避免卡顿。
    • 使用NSProgress类跟踪进度。

通过以上步骤,你可以在Objective-C环境中实现稳定可靠的断点续传功能。根据具体需求选择合适的方法,并注意处理边界条件和错误情况。

相关文章

  • Android-单线程断点续传

    断点续传的原理 看上一篇Android-断点续传 下面的例子是下载的断点续传,断点续传很简单就两点:1.网络请求的...

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

    断点续传 demo 断点续传的原理是在HTTP1.1协议(RFC2616)中定义了断点续传相关的HTTP头的Ran...

  • Okhttp多线程断点续传

    目录 1、断点续传相关定义2、多线程下载实现方案 1、断点续传相关定义 1.1、断点续传: 记录上次下载的位置,下...

  • NSURLSession实现断点下载

    断点续传概述 断点续传就是从文件上次中断的地方开始重新下载或上传数据,而不是从文件开头。(本文的断点续传仅涉及下载...

  • 多线程断点

    Android多线程断点续传下载 原理 其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下...

  • IOS 断点续传原理浅析(第一篇)

    断点续传概述: 断点续传就是从文件上次中断的地方开始重新下载或上传数据,当下载大文件的时候,如果没有实现断点续传功...

  • iOS-16 断点续传 下载

    断点续传概述: 断点续传就是从文件上次中断的地方开始重新下载或上传数据,当下载大文件的时候,如果没有实现断点续传功...

  • android 中断点续传

    android 中断点续传 单线程断点续传 所谓的断点续传就是在下载一个文件时,文件没有完全下载,中途暂停,那么再...

  • Android断点下载小结

    前言 断点续传是一个很传统的话题;现在但凡包含下载功能的软件,大部分都会有断点续传的功能;因此对于断点续传的实现,...

  • Android-多线程断点续传

    简介 多线程断点续传便是在单线程的断点续传上延伸的。多线程断点续传是把整个文件 分割成几个部分,每个部分由一条线程...

网友评论

      本文标题:断点续传

      本文链接:https://www.haomeiwen.com/subject/zilsmjtx.html