iOS网络(一)- 概览与NSURLSession

855 阅读4分钟

一、零碎基础知识

1、网络地址

  • ipv4 → 2^8*2^8*2^8*2^8-1 = 2^32 - 1 = 4G → 0~255 → 有限
  • ipv6 → 长度128是ipv4的4倍

2、请求结构

请求方法 空格 URL 空格 协议版本 回车符 换行符
头部字段名称 :(冒号) 回车符 换行符
....
头部字段名称 :(冒号) 回车符 换行符
空行(回车符 或 换行符)
请求包体 请求包体 请求包体 请求包体 请求包体 请求包体 请求包体

注:

​ 第1行: 请求行

​ 第2~4行: 请求头部

​ 第6行: 请求包体(一般常见的例子就是parameters)

http:请求行+请求头+请求体

3、传输过程

  1. 通过域名换取ip地址(dns解析)
  2. 建立连接 - 三次握手(tcp)
  3. head头部数据→空行→body数据
  4. 断开连接 - 四次挥手

4、tcp

  • 三次握手
  1. A : seq = x
  2. A → B
  3. B :ack = x + 1(确认) ; seq = y
  4. B → A
  5. A : ack = y + 1(确认)
  6. A → B(传数据)
  • 四次挥手
  1. A (fin_wait_1等待) → B : fin = x

  2. B (close_wait) → A (fin_wait_2) : ack = x + 1

    问:B 向 A发送后需要什么条件才又向A发送?需查资料

    答:此时虽然要断开,但是有可能在返回数据的过程中,所以需要一个等待的时机去断开

  3. B (last_ack) → A (closed) : fin = y

  4. A → B (closed) : ack = y + 1

  • 为什么握手要3次,挥手要4次?

5、OSI七层

数据会分段传输,每段MTU最大1500字节,最小576字节左右,其中tcp有20字节左右,MSS(536字节左右)

数据段一般46~1500字节不等

二、网络下载实践

1、概览

目标--控制网络下载:

  1. 暂停、继续、取消
  2. 断点续传
  3. 后台、程序死亡

2、NSURLSession

  • 基础下载与断点续传
//入口函数
- (void)downFile:(NSString*)fileUrl isBreakpoint:(BOOL)breakpoint
{
    
    if (!fileUrl || fileUrl.length == 0 || ![self checkIsUrlAtString:fileUrl]) 
    {
        NSLog(@"fileUrl 无效");
        return ;
    }
   // NSURLSession
    NSURL *url = [[NSURL alloc] initWithString:fileUrl];
    if (!self.session) 
    {
        // 网络会话session会有一个个的任务task,每个task都有taskid
      	// backgroundSessionConfigurationWithIdentifier会支持后台下载
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self currentDateStr]];
      //config为网络会话的配置,
        config.allowsCellularAccess = YES;//蜂窝网络
        config.timeoutIntervalForRequest = 30;//超时
      	//创建session
        self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
  
  	/*
  	if (!breakpoint)//是否支持断点续传
    {
        self.downloadTask = [self.session downloadTaskWithURL:url];
    }
    else
    {
        [self downloadWithResumeData];
    }
    */
    
    NSFileManager *fileManager = NSFileManager.defaultManager;
    // 是否支持断点续传应以是否存在 沙盒文件 为标准
    if (![fileManager fileExistsAtPath:[self getTmpFileUrl]]) 
    {
        self.downloadTask = [self.session downloadTaskWithURL:url];
    }
  	else
    {
        [self downloadWithResumeData];
    }
    
    // 开始建立连接 - 三次握手 - 重要
    [self.downloadTask resume];
    [self saveTmpFile];//预防app被kill的操作的代码
}

#pragma mark - NSURLSessionDelegate
//每次传一个包 调用一次该函数 512M
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //tmp
    float downPro   = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;
    // 该处使用了代理,也可使用block
    // manager中代理用于具体事务的处理
  	// manager将delegate和task绑定
  	// 注意不要用manager处理普通事务
    if(self.myDeleate && [self.myDeleate respondsToSelector:@selector(backDownprogress:tag:)])
        [self.myDeleate backDownprogress:downPro tag:self.tag];
}

//下载失败调用
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
{
    
    // 根据不同错误反馈不同 -- 错误码层(识别错误码)
    // 200-299皆为成功
    // 没有错误 → 序列化层 -- 能否支持http / json
    if(error && self.myDeleate && [self.myDeleate respondsToSelector:@selector(downError:tag:)] && error.code != -999)
        [self.myDeleate downError:error tag:self.tag];
}

#pragma mark - private

//暂停下载
-(void)suspendDownload
{
    // 播放/暂停/断点
    if (self.mIsSuspend) 
    {
        [self.downloadTask resume];
    }
  	else
    {
        [self.downloadTask suspend];
    }
    self.mIsSuspend = !self.mIsSuspend;
}

//取消下载
-(void)cancelDownload
{
  	// -999:用户取消
  	/* 一般取消下载:
  	[self.downloadTask cancel];
  	*/

  	// 断点续传需要在取消下载流程中做的操作
  	// 该block会返回目前已下载的data
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        //self.resumeData = resumeData; //放在内存里(此处伪代码会循环引用!)
        [resumeData writeToFile:[self getTmpFileUrl] atomically:YES];//写入沙盒
        self.downloadTask = nil;
    }];
}

//断点下载  ---  取消了但是不删除已下载的数据,下次接着下载
-(void)downloadWithResumeData
{
    if (!self.session) 
    {
      	//若没有session则不能续传
        return;
    }
    // 首先拿直线下载好的部分(沙盒、内存)
  	// defaultManager是类属性,有单例的特性
  	// @property (class, readonly, strong) NSFileManager *defaultManager;
    NSFileManager *fileManager = NSFileManager.defaultManager;
  	// 从沙盒中获取data
  	// 可改为.plist文件,断点续传有两种方法,还可以根据请求头来判别
    NSData        *dowloadData = [fileManager contentsAtPath:[self getTmpFileUrl]];
    // dowloadData = self.resumeData;// 从内存中取data
  	//在原来data基础上进行继续下载
    self.downloadTask  = [self.session downloadTaskWithResumeData:dowloadData];
    self.resumeData = nil; // 清除内存
}
  • 预防app被kill的操作
//提前保存临时文件 预防下载中杀掉app --- 比较笨的方法
//开启定时器
-(void)saveTmpFile
{
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(downloadTmpFile) userInfo:nil repeats:YES];
}
//杀掉app后 不至于下载的部分文件全部丢失
- (void)downloadTmpFile
{
    if (self.mIsSuspend) 
    {
        return;
    }
    
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        [resumeData writeToFile:[self getTmpFileUrl] atomically:YES];
        self.downloadTask = nil;
        self.downloadTask = [self.session downloadTaskWithResumeData:resumeData];
        [self.downloadTask resume];
    }];
}
				/*
      	- (void)applicationDidEnterBackground:(UIApplication *)application
				{
    				// 实现如下代码,才能使程序处于后台时被杀死,调用applicationWillTerminate:方法
    				[[UIApplication sharedApplication]beginBackgroundTaskWithExpirationHandler:
    				^(){}];
				}
      	*/

三、框架思维

最重要