[关闭]
@wrlqwe 2016-06-05T15:27:19.000000Z 字数 2499 阅读 934

文件断点续传

iOS 开发


前言

由于移动网络的不稳定性,为了节省用户的流量,提升用户体验,在做文件下载时,经常遇到断点续传的需求。

项目在以前其实有断点续传的实现,只不过并不完善,这次在重构过程中,iOS客户端放弃了之前被修改的千疮百孔的ASIHTTPRequest,转用iOS7开始提供的NSURLSession,在使用过程中,发现以前服务端配套的断点逻辑并不完善,两端配合完成了改造。

理论基础

当前的断点续传方案大多基于HTTP1.1,客户端上的大多数网络请求的第三方框架,都有良好的兼容性。而服务端在自定义文件传输时,往往要注意的比客户端要多,下面介绍断点续传的理论基础。

HTTP1.1 使用 RangeContent-Range Header来确定传输的资源的部分,它们分别在Request-Header和Response-Header里,指定要请求和要提供的部分。

Range 指定要请求实体(entity)的范围,和多数的程序规范一样,它也是从 0 开始计数的。比如前面已经下载了 500 bytes 的内容,要请求 500 bytes 以后的内容,只要在 HTTP 请求的头部加上 Range: bytes=500- 。

Range 有几种不同的方式来限定范围:

  1. 500-900:指定开始到结束这一段的长度,Range 是从 0 计数 的,所以这个是要求服务器从 501 字节开始传,一直到 901 字节结束。

  2. 500-:从 501 bytes 之后开始传,一直传到最后。

  3. -500:如果范围没有指定开始位置,就是要服务器端传递倒数 500 字节的内容。而不是从 0 开始的 500 字节。

  4. 500-900, 1000-2000:也可以同时指定多个范围的内容。

服务端通过 ETagLast-Modified 两个header,为该资源确定版本,以防下载文件不一致。

ETag: 用来唯一标识一个文件,它是一串字符,ETag机制同时支持强校验和弱校验。它们通过ETag标识符的开头是否存在“W/”来区分,在断点续传里都是强校验。

Last-Modified:就是这个文件上次修改的时间。

如果没有版本控制的需求,这两个字段服务端可以不加,这也是之前我们的服务端的实现方式。

如果要控制版本,提供一个就可以完成断点续传,自定义服务时,需要读取客户端提供的 ETag 或 Last-Modified 字段进行比较,通过 200 状态码告知客户端文件变更重新下载,206 告知客户端断点续传继续传输。每次服务端的返回里都要有 ETag 或 Last-Modified 字段。

iOS端NSURLSession如何使用

iOS7 开始,Apple 废弃了之前的NSURLConnection,改而推荐用 NSURLSession 实现网络请求,NSURLSession,NSURLSession 是一个强大的系统工具,原生支持断点续传。

NSURLSession 在开始下载时,提供了NSURLSessionDownloadTask,用来处理下载中用户的行为:

  1. public func downloadTaskWithRequest(request: NSURLRequest, completionHandler: (NSURL?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDownloadTask
  2. public func downloadTaskWithURL(url: NSURL, completionHandler: (NSURL?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDownloadTask
  1. public class NSURLSessionDownloadTask : NSURLSessionTask {
  2. /* Cancel the download (and calls the superclass -cancel). If
  3. * conditions will allow for resuming the download in the future, the
  4. * callback will be called with an opaque data blob, which may be used
  5. * with -downloadTaskWithResumeData: to attempt to resume the download.
  6. * If resume data cannot be created, the completion handler will be
  7. * called with nil resumeData.
  8. */
  9. public func cancelByProducingResumeData(completionHandler: (NSData?) -> Void)
  10. }

我们可以在下载中,调用 cancelByProducingResumeData 方法主动停止下载,并且保存下载中途的数据。

在合适的时候, Resume Download:

  1. public func downloadTaskWithResumeData(resumeData: NSData, completionHandler: (NSURL?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDownloadTask

NSURLSession只有这一个方法可以断点续传。而在非用户暂停,因为网络原因失败的下载,要如何获取下载中断的数据呢?

NSURLSession的API定义里,声明了一个 NSURLSessionDownloadTaskResumeData 字段,在失败的NSError的UserInfo里,提供了失败的请求data,允许我们在请求意外中断时保存数据:

  1. /* Key in the userInfo dictionary of an NSError received during a failed download. */
  2. public let NSURLSessionDownloadTaskResumeData: String

在这两个地方处理断点数据,无论是意外终端,还是用户暂停,都能很好的处理断点数据,辅助断点续传。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注