HTTP断点续传与断点上传之 -- 文件流操作

来源:互联网 发布:乐淘吧淘宝天猫商城 编辑:程序博客网 时间:2024/06/05 18:24

不管是下载还是上传,断点的时候,就需要对文件流进行精确的操作。
1、下载断开了,已经下载的数据保存到文件,再次继续下载的时候需要从文件的尾巴继续追加数据;
2、同理上传也是一样,http通信中有可能断开或者丢包的情况,就需要重传指定的文件片;

我封装的这个类,是基于把文件切成片的形式

FileStreamOperation.h

////  FileStreamOperation.h//  CommonProject////  Created by wuyoujian on 16/7/6.//  Copyright © 2016年 wuyoujian. All rights reserved.//#import <Foundation/Foundation.h>#define FileFragmentMaxSize         1024 * 512 // 512k@class FileFragment;/** *  文件流操作类 */@interface FileStreamOperation : NSObject<NSCoding>@property (nonatomic, readonly, copy) NSString *fileName;// 包括文件后缀名的文件名@property (nonatomic, readonly, assign) NSUInteger fileSize;// 文件大小@property (nonatomic, readonly, copy) NSString *filePath;// 文件所在的文件目录@property (nonatomic, readonly, strong) NSArray<FileFragment*> *fileFragments;// 文件分片数组// 若为读取文件数据,打开一个已存在的文件。// 若为写入文件数据,如果文件不存在,会创建的新的空文件。- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation ;// 获取当前偏移量- (NSUInteger)offsetInFile;// 设置偏移量, 仅对读取设置- (void)seekToFileOffset:(NSUInteger)offset;// 将偏移量定位到文件的末尾- (NSUInteger)seekToEndOfFile;// 关闭文件- (void)closeFile;#pragma mark - 读操作// 通过分片信息读取对应的片数据- (NSData*)readDateOfFragment:(FileFragment*)fragment;// 从当前文件偏移量开始- (NSData*)readDataOfLength:(NSUInteger)bytes;// 从当前文件偏移量开始- (NSData*)readDataToEndOfFile;#pragma mark - 写操作// 写入文件数据- (void)writeData:(NSData *)data;@end// 上传文件片@interface FileFragment : NSObject<NSCoding>@property (nonatomic,copy)NSString          *fragmentId;    // 片的唯一标识@property (nonatomic,assign)NSUInteger      fragmentSize;   // 片的大小@property (nonatomic,assign)NSUInteger      fragementOffset;// 片的偏移量@property (nonatomic,assign)BOOL            fragmentStatus; // 上传状态 YES上传成功@end

FileStreamOperation.m

////  FileStreamOperation.m//  CommonProject////  Created by wuyoujian on 16/7/6.//  Copyright © 2016年 wuyoujian. All rights reserved.//#import "FileStreamOperation.h"#import <CommonCrypto/CommonDigest.h>//// 把FileStreamOpenration类保存到UserDefault中//static NSString *const UserDefaultFileInfo = @"UserDefaultFileInfo";#pragma mark - FileStreamOperation@interface FileStreamOperation ()@property (nonatomic, copy) NSString                          *fileName;@property (nonatomic, assign) NSUInteger                      fileSize;@property (nonatomic, copy) NSString                          *filePath;@property (nonatomic, strong) NSArray<FileFragment*>          *fileFragments;@property (nonatomic, strong) NSFileHandle                    *readFileHandle;@property (nonatomic, strong) NSFileHandle                    *writeFileHandle;@property (nonatomic, assign) BOOL                            isReadOperation;@end@implementation FileStreamOperation+ (NSString *)fileKey {    CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);    CFStringRef cfstring = CFUUIDCreateString(kCFAllocatorDefault, uuid);    const char *cStr = CFStringGetCStringPtr(cfstring,CFStringGetFastestEncoding(cfstring));    unsigned char result[16];    CC_MD5( cStr, (unsigned int)strlen(cStr), result );    CFRelease(uuid);    return [NSString stringWithFormat:            @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%08lx",            result[0], result[1], result[2], result[3],            result[4], result[5], result[6], result[7],            result[8], result[9], result[10], result[11],            result[12], result[13], result[14], result[15],            (unsigned long)(arc4random() % NSUIntegerMax)];}- (void)encodeWithCoder:(NSCoder *)aCoder {    [aCoder encodeObject:[self fileName] forKey:@"fileName"];    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fileSize]] forKey:@"fileSize"];    [aCoder encodeObject:[self filePath] forKey:@"filePath"];    [aCoder encodeObject:[self fileFragments] forKey:@"fileFragments"];}- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {    self = [super init];    if (self != nil) {        [self setFileName:[aDecoder decodeObjectForKey:@"fileName"]];        [self setFileSize:[[aDecoder decodeObjectForKey:@"fileSize"] unsignedIntegerValue]];        [self setFilePath:[aDecoder decodeObjectForKey:@"filePath"]];        [self setFileFragments:[aDecoder decodeObjectForKey:@"fileFragments"]];    }    return self;}- (BOOL)getFileInfoAtPath:(NSString*)path {    NSFileManager *fileMgr = [NSFileManager defaultManager];    if (![fileMgr fileExistsAtPath:path]) {        NSLog(@"文件不存在:%@",path);        return NO;    }    self.filePath = path;    NSDictionary *attr =[fileMgr attributesOfItemAtPath:path error:nil];    self.fileSize = attr.fileSize;    NSString *fileName = [path lastPathComponent];    self.fileName = fileName;    return YES;}// 若为读取文件数据,打开一个已存在的文件。// 若为写入文件数据,如果文件不存在,会创建的新的空文件。- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation {    if (self = [super init]) {        self.isReadOperation = isReadOperation;        if (_isReadOperation) {            if (![self getFileInfoAtPath:path]) {                return nil;            }            self.readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path];            [self cutFileForFragments];        } else {            NSFileManager *fileMgr = [NSFileManager defaultManager];            if (![fileMgr fileExistsAtPath:path]) {                [fileMgr createFileAtPath:path contents:nil attributes:nil];            }            if (![self getFileInfoAtPath:path]) {                return nil;            }            self.writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:path];        }    }    return self;}#pragma mark - 读操作// 切分文件片段- (void)cutFileForFragments {    NSUInteger offset = FileFragmentMaxSize;    // 块数    NSUInteger chunks = (_fileSize%offset==0)?(_fileSize/offset):(_fileSize/(offset) + 1);    NSMutableArray<FileFragment *> *fragments = [[NSMutableArray alloc] initWithCapacity:0];    for (NSUInteger i = 0; i < chunks; i ++) {        FileFragment *fFragment = [[FileFragment alloc] init];        fFragment.fragmentStatus = NO;        fFragment.fragmentId = [[self class] fileKey];        fFragment.fragementOffset = i * offset;        if (i != chunks - 1) {            fFragment.fragmentSize = offset;        } else {            fFragment.fragmentSize = _fileSize - fFragment.fragementOffset;        }        [fragments addObject:fFragment];    }    self.fileFragments = fragments;}// 通过分片信息读取对应的片数据- (NSData*)readDateOfFragment:(FileFragment*)fragment {    if (fragment) {        [self seekToFileOffset:fragment.fragementOffset];        return [_readFileHandle readDataOfLength:fragment.fragmentSize];    }    return nil;}- (NSData*)readDataOfLength:(NSUInteger)bytes {    return [_readFileHandle readDataOfLength:bytes];}- (NSData*)readDataToEndOfFile {    return [_readFileHandle readDataToEndOfFile];}#pragma mark - 写操作// 写入文件数据- (void)writeData:(NSData *)data {    [_writeFileHandle writeData:data];}#pragma mark - common// 获取当前偏移量- (NSUInteger)offsetInFile{    if (_isReadOperation) {       return [_readFileHandle offsetInFile];    }    return [_writeFileHandle offsetInFile];}// 设置偏移量, 仅对读取设置- (void)seekToFileOffset:(NSUInteger)offset {    [_readFileHandle seekToFileOffset:offset];}// 将偏移量定位到文件的末尾- (NSUInteger)seekToEndOfFile{    if (_isReadOperation) {        return [_readFileHandle seekToEndOfFile];    }    return [_writeFileHandle seekToEndOfFile];}// 关闭文件- (void)closeFile {    if (_isReadOperation) {        [_readFileHandle closeFile];    } else {        [_writeFileHandle closeFile];    }}@end#pragma mark - FileFragment@implementation FileFragment- (void)encodeWithCoder:(NSCoder *)aCoder {    [aCoder encodeObject:[self fragmentId] forKey:@"fragmentId"];    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentSize]] forKey:@"fragmentSize"];    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragementOffset]] forKey:@"fragementOffset"];    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentStatus]] forKey:@"fragmentStatus"];}- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {    self = [super init];    if (self != nil) {        [self setFragmentId:[aDecoder decodeObjectForKey:@"fragmentId"]];        [self setFragmentSize:[[aDecoder decodeObjectForKey:@"fragmentSize"] unsignedIntegerValue]];        [self setFragementOffset:[[aDecoder decodeObjectForKey:@"fragementOffset"] unsignedIntegerValue]];        [self setFragmentStatus:[[aDecoder decodeObjectForKey:@"fragmentStatus"] boolValue]];    }    return self;}@end

值得注意的地方,HTTP的断点续传HTTP协议是支持的,但是需要前后端配套开发。但断点上传就需要自定义的方式上传,HTTP协议是不支持的,所以也是需要前后端配套开发。

后面我会补充支持断点的网络通信那一块代码!

1 0
原创粉丝点击