多线程下载单个文件,有人会问,为什么要用多线程下载一个文件?
因为有时候单个线程不能使带宽的充分利用,因为服务器限速了,这个时候用多线程就可以了,基本都能达到带宽的最大速度.
如果有问题,希望可以留言.源代码在Github上,可以自己下载.
核心代码:
/// Download file public class Download: NSObject, NSURLSessionDownloadDelegate { private var url:String private weak var delegate:downloadDelegate? private var task:NSURLSessionDownloadTask? private var thread:Int private var filelocation:String private var fbegin:Int64 private var fend:Int64 init(url:String, filelocation:String, delegate:downloadDelegate?, _ thread:Int, _ fbegin:Int64, _ fend:Int64) { self.url = url self.delegate = delegate self.thread = thread self.filelocation = filelocation self.fbegin = fbegin self.fend = fend } public func xdownload() { let requst = NSMutableURLRequest(URL: NSURL(string: url)!) assert(fbegin < fend) requst.setValue("bytes=\(fbegin)-\(fend)", forHTTPHeaderField: "Range")/* Set rang to get different data */ let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: nil) task = session.downloadTaskWithRequest(requst)/*, completionHandler: { (url, response, error) -> Void in session.finishTasksAndInvalidate()/* Invalidates the object */ print("Close") })*/ assert(task != nil, "task is nil") task?.resume() } public func xSuspend() { task?.suspend() } public func xResume() { task?.resume() } public func xStop() { task?.cancel() } public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { let progressPercent = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) if let progressdelegate = delegate { progressdelegate.refresh(thread, threadprogress: progressPercent) } //print("\(thread):\(totalBytesWritten)bytes \(totalBytesExpectedToWrite)bytes") } public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { session.finishTasksAndInvalidate()/* Invalidates the object */ guard let filepath = location.path else { assert(false, "Get file location error"); return } guard let readhandle = NSFileHandle(forReadingAtPath: filepath) else { assert(false, "Read file error"); return } guard let writehandle = NSFileHandle(forWritingAtPath: filelocation) else { assert(false, "Write file error"); return } print(filepath) writehandle.seekToFileOffset(UInt64(fbegin)) /// move file to destination var currentLength:UInt64 = 0 while true { let data = readhandle.readDataOfLength(1024) writehandle.writeData(data) if data.length < 1024 { break } currentLength += 1024 readhandle.seekToFileOffset(currentLength) writehandle.seekToFileOffset(currentLength) } writehandle.closeFile() readhandle.closeFile() /// delete temp file let filemanager = NSFileManager.defaultManager() do { try filemanager.removeItemAtPath(filepath) }catch { assert(false, "Remove file error") } } }
多线程下载(创建4个线程分别下载同一个文件的不同部分):
每当线程下载完成,就将读取下载的临时文件,然后写到Downloads位置
最终拼接成一个文件.
线程采用的是异步,所以线程之间是抢占资源.
public class MutiDownload { private var url:String private var fileinfo:fileInfo? private weak var delegate:downloadDelegate? private var download = [Download]() public var name:String? { return fileinfo == nil ? nil : fileinfo!.name } public var size:Int64? { return fileinfo == nil ? nil : fileinfo!.size } init(url:String, delegate:downloadDelegate?) { self.url = url self.delegate = delegate fileinfo = xGetFileInfo(url) assert(fileinfo != nil, "Can't get file infomation") //guard fileinfo != nil else { return nil } } /** To get file information about file size and file name - returns: fileInfo */ private func xGetFileInfo(url:String) -> fileInfo? { let requst = NSMutableURLRequest(URL: NSURL(string: url)!) requst.HTTPMethod = Method.HEAD.rawValue var response:NSURLResponse? do { try NSURLConnection.sendSynchronousRequest(requst, returningResponse: &response) return fileInfo(size: response!.expectedContentLength, name: response!.suggestedFilename) }catch { assert(response != nil, "get file information error") }/* URL error or Network Error */ return nil } public func xMutiDownload() { let home = NSHomeDirectory() let filepath = home + "/Downloads/\(name!)" /// Remove file if it exist let fileManager = NSFileManager.defaultManager() if fileManager.fileExistsAtPath(filepath) { do { try fileManager.removeItemAtPath(filepath) }catch { assert(false, "Remove file error") } } /// Create an empty file fileManager.createFileAtPath(filepath, contents: nil, attributes: nil) if let filehandle = NSFileHandle(forWritingAtPath: filepath) { filehandle.truncateFileAtOffset(UInt64(size!)) filehandle.closeFile() } let group = dispatch_group_create() let queue = dispatch_queue_create("com.download.xwjack", DISPATCH_QUEUE_SERIAL) dispatch_group_async(group, queue, { self.download.append(Download(url: self.url, filelocation: filepath, delegate: self.delegate, 1, 0, self.fileinfo!.size / 4)) self.download[0].xdownload() }) dispatch_group_async(group, queue, { self.download.append(Download(url: self.url, filelocation: filepath, delegate: self.delegate, 2, self.fileinfo!.size / 4 + 1, self.fileinfo!.size / 4 * 2)) self.download[1].xdownload() }) dispatch_group_async(group, queue, { self.download.append(Download(url: self.url, filelocation: filepath, delegate: self.delegate, 3, self.fileinfo!.size / 4 * 2 + 1, self.fileinfo!.size / 4 * 3)) self.download[2].xdownload() }) dispatch_group_async(group, queue, { self.download.append(Download(url: self.url, filelocation: filepath, delegate: self.delegate, 4, self.fileinfo!.size / 4 * 3 + 1, self.fileinfo!.size)) self.download[3].xdownload() }) dispatch_group_notify(group, queue, { print("All Thread Begin Download") }) } public func xSuspend() { for download in self.download { download.xSuspend() } } public func xResume() { for download in self.download { download.xResume() } } public func xStop() { for download in self.download { download.xStop() } } }
源代码各部分都有注释.
如何获得文件信息?
HTTP协议HEAD中有一个字段为Range,设置Range的值就可以请求对应的数据部分,最后合并成一个文件.
public func xdownload() { let requst = NSMutableURLRequest(URL: NSURL(string: url)!) assert(fbegin < fend) requst.setValue("bytes=\(fbegin)-\(fend)", forHTTPHeaderField: "Range")/* Set rang to get different data */ let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: nil) task = session.downloadTaskWithRequest(requst)/*, completionHandler: { (url, response, error) -> Void in session.finishTasksAndInvalidate()/* Invalidates the object */ print("Close") })*/ assert(task != nil, "task is nil") task?.resume() }
程序使用的方法都是封装好的类,只要适当调用就可以了,之后我会继续用C语言写(深入底层用套接字,不适用封装好的类,并在博客上更新.),使用封装好的类有时候是很方便,但是有时候也不方便.
如果想深入了解实现过程,还是应该用C语言写.当然最后还是C语言和Swift混合编程.
Github项目地址:MutiThreadDownload
创建时间:2016-3-17
程序采用多线程异步下载单个文件.
如果转载请注明出处和原文地址(http://www.cnblogs.com/xwjack1554239786/p/5289582.html).谢谢.