美文网首页
iOS开发经验(13)-网络

iOS开发经验(13)-网络

作者: Ryan___ | 来源:发表于2017-02-14 15:32 被阅读190次

    目录

    1. 网络基本概念
    2. TCP/IP协议簇基本概念
    3. HTTP
    4. 网络开发技术解决方案
    5. 数据解析
    6. 网络优化
    1. 网络基本概念

    为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型。它将计算机网络体系结构的通信协议划分为七层:

    • 模型分层思想:

    | 协议 | 具体
    | -------------
    | 应用层 | OSI 的应用层协议包括文件的传输、访问及管理协议(FTAM) ,以及文件虚拟终端协议(VIP)和公用管理系统信息(CMIP)等;规定数据的传输协议;eg:RJ45等将数据转化成0和1
    | 表示层 | 表示层供多种功能用于应用层数据编码和转化,以确保以一个系统应用层发送的信息 可以被另一个系统应用层识别,可以理解为:解决不同系统之间的通信,eg:Linux下的QQ和Windows下的QQ可以通信􏰁
    | 会话层 | 会话层建立、管理和终止表示层与实体之间的通信会话;建立一个连接(自动的手机信息、自动的网络寻址);
    | 传输层 | 传输层向高层􏰁提供可靠的端到端的网络数据流服务。可以理解为:每一个应用程序都会在网卡注册一个端口号,该层就是端口与端口的通信!常用的(TCP/IP)协议;
    | 网络层 | 网络层负责在源和终点之间建立连接;可以理解为,此处需要确定计算机的位置,怎么确定?IPv4,IPv6!
    | 数据链路层 | 数据链路层通过物理网络链路􏰁供数据传输。不同数据链路层定义了不同的网络和协 议特征,其中包括物理编址、网络拓扑结构、错误校验、数据帧序列以及流控;可以简单的理解为:规定了0和1的分包形式,确定了网络数据包的形式;
    | 物理层 | 物理层负责最后将信息编码成电流脉冲或其它信号用于网上传输;eg:RJ45等将数据转化成0和1;

    其中第四层完成数据传送服务,上面三层面向用户。对于每一层,至少制定两项标准:服务定义和协议规范。前者给出了该层所提供的服务的准确定义,后者详细描述了该协议的动作和各种有关规程,以保证服务的提供。

    网络层次分层.png
    2. TCP/IP协议簇基本概念

    基本概念
    TCP/IP协议簇(协议簇通常指彼此相关联的一系列协议的总称),译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
    不同于OSI/RM的七层结构,TCP/IP模型是一个四层模型,从上到下依次是:

    应用层
    传输层
    网络层
    物理链路层
    

    值得一提的是,传输层可以使用两种不同的协议,一个是面向连接的传输控制协议(TCP),另一个是无连接的用户数据报协议(UDP),我们耳熟能详的的协议如HTTP、FTP、POP3、DNS等都属于应用层的协议,它们要么构建在TCP之上,要么构建在UDP之上。

    | 协议 | 具体
    | -------------
    | 应用层协议 | FTP · HTTP· XMPP ·POP3 · SMTP
    | 传输层协议 | TCP · UDP · TLS
    | 网络层协议 | IP (IPv4 · IPv6)
    | 物理链路层 | Wi-Fi· GPRS·以太网 · 调制解调器

    常见的应用层协议:

    | 协议 | 端口 | 说明
    | -------------
    | HTTP | 80 | 超文本传输协议
    | HTTPS | 443 | HTTP+SSL
    | POP3 | 110 | 邮件协议

    传输层
    传输层(Transport Layer)是TCP/IP协议簇(四层模型)中最重要、最关键的一层,它负责总体的数据传输和数据控制的一层,传输层提供端到端(应用会在网卡注册一个端口号)的交换数据的机制,检查分组编号与次序。传输层对其上三层如会话层等,提供可靠的传输服务,对网络层提供可靠的目的地站点信息。

    • 传输层的核心协议是TCP和UDP。
    • 传输层它为应用层提供会话和数据报通信服务。
    • 传输层承担OSI传输层的职责。

    TCP

    • TCP基本概念
      TCP提供一对一的、面向连接的可靠通信服务。TCP建立连接,对发送的数据包进行排序和确认,并恢复在传输过程中丢失的数据包。与TCP不同,UDP提供一对一或一对多的、无连接的不可靠通信服务。
      不论是TCP/IP还是在OSI参考模型中,任意相邻两层的下层为服务提供者,上层为服务调用者。下层为上层提供的服务可分为两类:面向连接服务和无连接服务。

    • TCP工作原理-三次握手**
      TCP的连接建立过程又称为TCP三次握手;
      首先发送方主机向接收方主机发起一个建立连接的同步(SYN)请求;
      接收方主机在收到这个请求后向发送方主机回复一个同步/确认(SYN/ACK)应答;
      发送方主机收到此包后再向接收方主机发送一个确认(ACK),此时TCP连接成功建立.
      一旦初始的三次握手完成,在发送和接收主机之间将按顺序发送和确认段。关闭连接之前,TCP使用类似的握手过程验证两个主机是否都完成发送和接收全部数据。
      完成三次握手,客户端与服务器开始传送数据

    UDP

    • UDP全称是User Datagram Protocol,中文名为用户数据报协议。UDP 提供无连接的网络服务,该服务对消息中传输的数据提供不可靠的、最大努力传送。这意味着它不保证数据报的到达,也不保证所传送数据包的顺序是否正确。
      我最初就有一个疑惑:“既然UDP是一种不可靠的网络协议,那么还有什么使用价值或必要呢?”
      在有些情况下UDP可能会变得非常有用。因为UDP具有TCP所望尘莫及的速度优势。虽然TCP中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销,无疑使速度受到严重的影响。反观UDP由于排除了信息可靠传递机制,将安全和排序等功能移交给上层应用来完成,极大地降低了执行时间,使速度得到了保证。

    IP地址
    IP地址[主机名],英文全称:Internet Protocol Address,又译为网际协议地址。IP协议的作用就是把各种数据包传给对方。为了能确保准确送达,所以需要IP地址和MAC地址,IP地址指明了节点被分配的地址,MAC地址指的是网卡所属的固定地址,IP地址可变换,但是MAC地址是不会改变的。所以IP间的通信就需要MAC地址,这里就需要知道IP地址和MAC地址的对应关系,所以又出现了另一种协议ARP(Address Resolution Protocol),能通过IP地址查询到MAC地址。
    1.网络中设备的标识,用来唯一标识每一台计算机。通常现在常用的IP地址是IPV4地址。
    2.IPV4就是有4段数字,格式是xxx.xxx.xxx.xxx,每一段数字由8位二进制做成,取值范围是0~255。
    3.IPV4采用32位地址长度,只有大约43亿个地址,IPv4定义的有限地址空间将被耗尽。
    4.为了扩大地址空间,拟通过IPV6重新定义地址空间。IPv6采用128位地址长度,几乎可以不受限制地提供地址,但IPV6现在还没有正式普及。
    为了解决IPV4有限地址空间的问题,IP地址又分内网地址和外网地址。(比如校园网,每一个学生都会有一个内网地址,学校会有一个路由器,路由器会有个外网地址,学生想要上外网都必须通过路由器出去,只要通过同一个路由器出去的,他们对应的外网地址都是一样的)
    本质上所有的网络访问是通过ip地址访问的,域名是一个速记符号,不用记住IP地址复杂的数字。
    本地回环地址:127.0.0.1 主机名:localhost
    5.每台计算机都有一个127.0.0.1
    如果127.0.0.1 ping不通,说明网卡不工作(比如装黑苹果,检测网卡驱动有没装好,可以 ping下回环地址)
    如果本机地址 ping不通,说明网线坏了。

    端口号
    端口号用来标识进程的逻辑地址,不同进程的标识。
    有效的端口:0~65535。
    其中0~1024由系统使用或保留端口。
    开发中不要使用1024以下的端口。
    (端口有什么用呢?我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区 分不同的服务的。)

    ** DNS 「Domain Name System」域名系统协议**
    DNS 就是进行域名解析的服务器
    DNS 命名用于 Internet 等 TCP/IP 网络中,通过用户友好的名称查找计算机和服务
    用于命名、组织到域层次结构中的计算机和网络服务,可以简单地理解为 将 URL 转换为 IP 地址
    域名:是由圆点分开一串单词或缩写组成的,域名与 IP 地址之间是一一对应的

    Socket 简介
    Socket(套接字)是通信的基石,是 应用层 和 传输层 之间的桥梁,是支持TCP/IP协议的网络通信的基本操作单元,网络通信其实就是 Socket 间的通信「数据在两个 Socket 间通过 IO 传输」。
    HTTP 支持短链接 Socket 支持长链接 用于IM。
    Socket包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

    通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际应用中,客户端到服务器之间的通信防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

    而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

    HTTP 与 Socket 的区别

    • Socket 是对 TCP/IP 协议的封装,Socket 本身并不是协议,而是一个调用接口「API」
    • 通过 Socket 我们才能使用 TCP/IP 协议
    • HTTP 是基于 Socket 的实现;HTTP 应用层协议,主要解决如何包装数据
    • HTTP 传输的数据格式是规定好的,Socket 实现数据传输是最原始,Socket 实现的数据传输格式可自定义
    Socket通信.png
    Socket 的连接过程
    长连接:指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包
    短连接:指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送
    3. HTTP协议

    后来发明了浏览器,浏览器通过超文本传输协议(HTTP)跟服务器交换超文本数据,通过图形用户界面显示从服务器获得的超文本数据,这一切都让使用Internet变得无比简单,于是计算机网络的用户数量开始爆炸式的增长。
    我们先来解释一下什么是协议以及HTTP到底是一个怎样的协议。我们将任何可发送或接收信息的硬件或程序称之为实体,而协议则是控制两个对等实体进行通信的规则的集合。简单的说,协议就是通信双方必须遵循的对话的标准和规范。HTTP是构建在TCP之上的协议,之所以选择TCP作为底层传输协议是因为TCP除了可以保证可靠通信之外,还具备流量控制和拥塞控制的能力,如果这一点不能理解也不要紧,我么只需要知道HTTP需要可靠的传输层协议的支持就够了。

    再次重复一遍:
    超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。
    HTTP协议规定了客户端和服务器之间的数据传输格式.

    HTTP优点:

    • 简单快速: HTTP协议简单,通信速度很快;
    • 灵活: HTTP协议允许传输任意类型的数据;
    • 短连接: HTTP协议限制每次连接只处理一个请求,服务器对客户端的请求作出响应后,马上断开连接.这种方式可以节省传输时间.

    HTTP请求/响应:
    HTTP有两种类型的报文:请求报文和响应报文。请求报文和响应报文都是由三个部分组成的。

    请求报文是由请求行、请求头和消息体构成的。

    • 请求行包含了命令(通常是GET或POST)、资源和协议版本;
    • 请求头是键值对映射形式的和请求相关的信息,如客户端使用的语言、使用的浏览器等信息;
    • 消息体是客户端发给服务器的数据;在请求头和消息体之间有一个空行。

    响应报文是由响应行、响应头和消息体构成的。

    • 响应行包含了协议版本和状态码;
    • 响应头是键值对形式的和响应相关的信息,如服务器的软件版本、时间日期、缓存策略、响应内容类型等信息;
    • 消息体是服务器发给客户端的数据;在响应头和消息体之间有一个空行。

    HTTP基本通信过程

    请求:客户端向服务器索要数据
    响应:服务器返回客户端相应的数据
    1.确定请求路径url
    http://www.baidu.com:80/tools.html
    2.获取主机名
    www.baidu.com
    3.DNS域名解析
    192.168.31.1
    4.获得端口号80
    5.连接到192.168.31.1的端口80
    6.发送HTTP GET请求 
    7.接收到服务器的响应
    8.关闭连接
    

    HTTP请求方法
    HTTP:是应用层的网络传输协议

    • 对于HTTP的请求方式包括
    eg:GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT、PATCH
    

    其中最常用的是GET和POST方法。
    因为GET和POST可以实现上述所有操作,所以,在现实开发中,GET和POST方法使用的最为广泛,除此以外HEAD请求使用频率也比较高。

    • 连接方式也包括两种:同步连接和异步连接
      此外定义了其他方法对应不同的资源操作:

    GET和POST的区别:
    1.GET请求:服务器的地址和请求参数 都会出现在请求接口中,也就是说 服务器地址和请求参数共同组成了请求接口,而POST请求,请求参数不会出现在请求接口中,作为请求体,提交给路由器。
    2.因为GET请求的请求参数会出现请求接口中所以信息容易被捕获,安全性低,而POST请求的请求参数封装在请求体中,作为二进制流进行传输,安全性高。
    3.GET请求的请求接口中,有请求参数,而对于请求接口我们有字节限制,这样我们导致GET请求有一定局限性。所以,对于GET请求只能上传小型数据,而对于POST请求,请求体 理论上可以无限大,所以一般来说,从服务器请求数据用GET,上传数据用POST。

    相关API
    NSURL
    URL的格式如下所示:

    协议://域名或IP地址:端口号/路径/资源
    

    下面是百度logo的URL:

    http://www.baidu.com:80/img/bd_logo1.png
    

    URL的基本格式 = 协议://主机地址/资源路径

    http://www.example.com/img/logo.png
    ----- ================ ------------
    协议名        主机地址      资源路径
    
    http://8.8.8.8:8080/img/bdlogo.gif
    ----- ============= ---------------
    协议名  主机地址+端口    资源路径
    

    说明:端口号是对IP地址的扩展。例如我们的服务器只有一个IP地址,但是我们可以在这台服务器上开设多个服务,如Web服务、邮件服务和数据库服务,当服务器收到一个请求时会根据端口号来区分到底请求的是Web服务还是邮件服务,或者是数据库服务。我们在浏览器中输入URL的时候通常都会省略端口号,因为HTTP协议默认使用80端口,也就是说除非你访问的Web服务器没有使用80端口,你才需要输入相应的端口号。

    状态码

    | 状态码 | 英文名称 | 描述
    | -------------
    | 200 | OK | 请求成功
    | 400 | Bad Request | 客户端请求的语法错误,服务器无法解析
    | 404 | Not Found | 服务器无法根据客户端的请求找到资源
    | 500 | Internal Server Error | 服务器内部错误,无法完成请求

    HTTPS简介
    HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

    4. 网络开发技术解决方案

    在iOS中,常见的发送HTTP请求的方案包括:

    • 苹果官方

    | 名称 | 说明
    | -------------
    | NSURLConnection | iOS 2.0 推出,用法简单,最古老最经典最直接的一种方案,在 iOS 9.0 被废弃
    | NSURLSession | iOS 7 推出,功能比 NSURLConnection 更加强大
    | CFNetwork |NSURL 的底层,纯C语言,几乎不用

    • 第三方框架

    | 名称 | 说明
    | -------------
    | AFNetworking |NSURLConnection & NSURLSession 简单易用,提供了基本够用的常用功能,维护和使用者多

    1.NSURLConnection
    在iOS 7以前,基于HTTP协议联网的操作最终都要由NSURLConnection类来完成。
    利用NSURLConnection进行网络请求分为3步:

    • 创建请求路径(NSURL)
    • 创建请求对象(NSURLRequest)
    • 发送请求(NSURLConnection)

    发送请求主要有两个方法,一个用于发送同步请求,一个用于发送异步请求。

    // 发送同步请求的方法:该方法是阻塞式的会卡住线程。
    + (NSData *)sendSynchronousRequest:(NSURLRequest *)request 
          returningResponse:(NSURLResponse **)response error:(NSError **)error;
    
    // 发送异步请求的方法
    + (void)sendAsynchronousRequest:(NSURLRequest *)request 
          queue:(NSOperationQueue *)queue 
          completionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *connectionError))handler
    

    提示:同步请求是阻塞式请求,这就意味着同步请求的方法在返回数据之前会一直阻塞;异步请求是非阻塞式请求,当服务器返回数据时可以回调的方式对数据进行处理。如果明白这一点,就很容易理解为什么上面的同步请求方法会返回NSData指针,而异步请求方法没有返回值但有一个Block类型的参数(Block最适合用来书写回调代码)。

    1.get 同步:代码正常写法
    get 异步:block写法;get异步:代理写法

    2.post同步:代码正常写法
    post异步 :代理写法; post异步:block写法

    3.同步下,都是代码正常写法,异步下,才会用代理或者block
    get,post跟数据安全及大小有关系,跟线程无关系。同步异步只跟线程有关系。
    4.代理相较于block优势:下载文件可监听文件下载进度

    2.NSURLSession
    2013的WWDC,苹果推出了NSURLConnection的继任者NSURLSession。与NSURLConnection相比,NSURLsession最直接的改进就是可以配置每个会话(session)的缓存、协议、cookie以及证书策略(credential policy)等,而且你可以跨程序共享这些信息。每个NSURLSession对象都由一个NSURLSessionConfiguration对象来进行初始化,NSURLSessionConfiguration对象代表了会话的配置以及一些用来增强移动设备上性能的新选项。

    URL加载系统的API.png

    NSURLSession优势:(相较于NSURLConnection)

    • 支持后台上传和下载:在程序在前台时,NSURLSession与NSURLConnection可以相互的替代。但是当用户在对程序进行强制关闭的时候此时NSURLSession会默认的自动断开。相比而言NSURLSession的优势主要体现在后台操作时候,而且在最流行的框架AFNetworking中也对NSURLSession提供了更好的支持。
    • 在处理下载任务的时候可以直接把数据下载到磁盘:NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况。
      而使用NSURLSessionUploadTask下载文件,会默认下载到沙盒中的tem文件中,不会出现内存暴涨的情况,但是在下载完成后会把tem中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码。(后面会详细说)
    • 下载速度: 支持HTTP 2.0协议,HTTP/2比HTTP/1.1在速度上快很多
    • 能够暂停和恢复网络操作:暂停以后可以通过继续恢复当前的请求任务。
    • 同一个 session 发送多个请求,只需要建立一次连接(复用了TCP);
    • 下载的时候是多线程异步处理,效率更高;
    • 可配置容器:提供了全局的 session 并且可以统一配置,使用更加方便。对于NSURLSession里面的requests来说,每个NSURLSession都是可配置的容器。举个例来说,假如你需要设置HTTP header选项,你只用做一次,session里面的每个request就会有同样的配置。
    • 提高认证处理:认证是在一个指定的连接基础上完成的。在使用NSURLConnection时,如果发出一个访问,会返回一个任意的request。此时,你就不能确切的知道哪个request收到了访问。而在NSURLSession中,就能用代理处理认证。

    NSURLSession主要有三个类:

    • NSURLSession:NSURLSession类以及相关类提供了一组用于通过HTTP下载内容的API,这组API中具有许多委托方法用来进行身份验证、后台下载等活动,具有异步性。
    • NSURLSessionConfiguration:该类定义了使用NSURLSession对象进行上传和下载时的行为和策略,在使用NSURLSession对象时,总是要首先定义该类对象。
    • NSURLSessionTask:该类是NSURLSession中所有任务的基类,task是session的一部分。任务通过调用NSURLSession对象中的方法进行创建。

    注:NSURLSession的行为由session的类型(取决于NSURLSessionConfiguration类型决定)、task类型以及创建task时,App是运行在前台还是后台

    1. NSURLSession
    NSURLSession 本身是不会进行请求的,而是通过创建 task 的形式进行网络请求,同一个 NSURLSession 可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。那么我们就来看看 NSURLSession 都能创建哪些 task 吧。

    2. NSURLSessionConfiguration:会话Session控制
    如前面所述,NSURLSessionConfiguration代表了会话的配置,该类的三个创建对象的类方法很好的诠释了NSURLSession类设计时所考虑的不同的使用场景。
    一般情况下全局的Session我们够用了,但是如果遇到两个连接使用不同配置的情况,就得重新配置session
    NSURLSession支持的3种会话配置:

    1. defaultSessionConfiguration (default)
      进程内会话(默认会话),用硬盘来缓存数据
    2. ephemeralSessionConfiguration (ephemeral)
      临时的进程内会话,不会将cookie,缓存储存到本地,只会放在内存中,当应用程序退出后数据也会消失。(浏览器的无痕浏览模式)
    3. backgroundSessionConfiguration (background)
      后台会话,相比默认会话,该会话会在后台开启一个线程进行网络数据处理。
    // 返回一个标准的配置,标准配置会使用默认的缓存策略、超时时间等
    + (NSURLSessionConfiguration *)defaultSessionConfiguration;
    // 返回一个临时性的配置,这个配置中不会对缓存,Cookie和证书进行持久化存储
    // 对于实现无痕浏览这种功能来说这种配置是非常理想的
    + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
    // 返回一个后台配置
    // 后台会话不同于普通的会话,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务
    // 初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文
    + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:
          (NSString *)identifier
    

    常规配置属性 :

    /* 常规属性 - 常规属性有7个  */ 
    /*background 模式下需要标识 */
    @property (nullable, readonly, copy) NSString *identifier;
    
    /*超时属性 ,用作设置timeoutIntervalForRequest时间内, 如果没有请求数据发送,则请求超时 */
    @property NSTimeInterval timeoutIntervalForRequest;
    
    /* 超时属性, 用作设置timeoutIntervalForResource时间内,如果没有返回响应,则响应超时 */
    @property NSTimeInterval timeoutIntervalForResource;
    
    /* 网络服务类型 */
    typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
    {
        NSURLNetworkServiceTypeDefault = 0, // Standard internet traffic
        NSURLNetworkServiceTypeVoIP = 1, // Voice over IP control traffic  
        NSURLNetworkServiceTypeVideo = 2, // Video traffic
        NSURLNetworkServiceTypeBackground = 3, // Background traffic
        NSURLNetworkServiceTypeVoice = 4    // Voice data
    };
    // 这个属性一般用于音频处理流
    @property NSURLRequestNetworkServiceType networkServiceType;
    
    // 设置这个属性, 会为所有的NSURLSessionTask加上基础request头,可以统一请求头
    @property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;
    
    // 一个布尔值,确定是否通过蜂窝网络连接, 默认为YES , 
    @property BOOL allowsCellularAccess;
    
    // iOS8.0后才能用, backgroundSessionConfigurationWithIdentifier中的identifier中对应使用, 被标识的文件,会在后台中下载
    @property (nullable, copy) NSString *sharedContainerIdentifier NS_AVAILABLE(10_10, 8_0);
    

    使用NSURLSessionConfiguration配置一个默认session的具体方法

    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    sessionConfig.allowsCellularAccess = true;//是否允许蜂窝网络下载(2G/3G/4G)
    [sessionConfig setHTTPAdditionalHeaders: @{@"Accept": @"application/json"}];//所有的请求只接收JSON数据
    sessionConfig.timeoutIntervalForRequest = 25.0f;//请求超时时间
    sessionConfig.timeoutIntervalForResource = 60.0;
    sessionConfig.HTTPMaximumConnectionsPerHost = 1;//限制一个主机只有一个网络连接。
    
    //创建会话,指定配置和代理
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig 
                                                      delegate:self 
    

    3. NSURLSessionTask
    NSURLSession在使用上需要先根据会话对象(NSURLSession)创建一个请求NSURLSessionTask(会话任务。NSURLSessionTask是抽象类,我们需要使用它的子类,先看看关系:

    NSURLSession类继承关系.png

    会话任务有三个子类对应不同的场景,分别是:

    • NSURLSessionDataTask(获取数据的任务):使用NSData对象收发数据,主要用于CS之间的临时请求。数据任务可以在每次接收到数据后将数据提交给App,也可以通过处理程序一次将所有数据返回给App。
    • NSURLSessionDownloadTask(下载任务):以文件的形式检索数据,支持后台下载。
    • NSURLSessionUploadTask(上传任务):以文件形式发送数据,支持后台上传。

    说明:代理相较于block优势:下载文件可监听文件下载进度!

    已经有了NSURLSessionDataTask, 为什么还有继承多一层NSURLSessionUploadTask?

    答:简化了操作!从两者提供的对应task 生成的方法能看。比如使用dataTask来进行上传任务的时候,需要指定HTTPMethod为POST或PUT,并且提供的数据(NSData)得赋值给request.HTTPBody。而使用uploadTask来进行上传任务的时候,只需要使用- uploadTaskWithRequest:fromData:或- uploadTaskWithRequest:fromFile:之类的方法,其中参数的话只需要根提供数据(NSData)或者数据的磁盘位置(NSURL*fileURL)就可以构造出一个上传的session task了,简化了操作。

    我们通过HTTP协议可以完成的操作都属于这三类任务之一。

    NSURLSessionTask主要有三个任务操作方法,分别是:

    • resume(恢复任务)
    • suspend(挂起任务)
    • cancel(取消任务)

    NSURLSession一般使用方法:

    • 创建一个URL
    • 创建NSURLRequest请求
    • 创建会话:NSURLSession
    • 通过会话创建务Task
    • 通过Task调用resume方法启动任务。
    #import "NetRequestViewController.h"
    
    @interface NetRequestViewController ()<NSURLSessionDelegate,NSURLSessionDataDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate>
    
    @property (nonatomic,strong) NSMutableData *dataM;
    
    @property (nonatomic,strong) NSURLSession *session;
    
    @end
    
    static NSString *urlstr =@"http://api.yanagou.net/app/web.json";
    
    static NSString *urlstr1 = @"http://api.yanagou.net/app/user/login.json";
    
    static NSString *urlstrimage = @"http://120.25.226.186:32812/resources/images/minion_01.png";
    
    static NSString *urlstrimage1 = @"http://120.25.226.186:32812/resources/images/minion_02.png";
    @implementation NetRequestViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
        
        //一般的三种请求方式
        //GET In Block
        //[self setDataTask_get];
        
        //POST In Block
        //[self setDataTask_post];
        
        //代理方法 In delegate
        //[self setDataTask_delegate];
        
        
        //下载方法
        //下载1. In Block
        [self setDataTask_1];
    
        //下载2. In Block
        [self setDownloadTask_2];
    }
    
    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        /*
         针对:setDataTask_delegate
         设置代理之后的强引用问题
         NSURLSession 对象在使用的时候,如果设置了代理,那么 session 会对代理对象保持一个强引用,在合适的时候应该主动进行释放
         可以在控制器调用 viewDidDisappear 方法的时候来进行处理,可以通过调用 invalidateAndCancel 方法或者是 finishTasksAndInvalidate 方法来释放对代理对象的强引用
         
         invalidateAndCancel 方法直接取消请求然后释放代理对象
         finishTasksAndInvalidate 方法等请求完成之后释放代理对象。
         */
        [self.session finishTasksAndInvalidate];
    
    }
    
    /*
            NSURLSessionDataTask 发送 GET 请求:
            发送 GET 请求的步骤非常简单,只需要两步就可以完成:
                1.使用 NSURLSession 对象创建 Task
                2.执行 Task
     
    */
    - (void)setDataTask_get
    {
        //确定请求路径
        NSURL *url = [NSURL URLWithString:urlstr];
        //创建 NSURLSession 对象
        NSURLSession *session = [NSURLSession sharedSession];
        
        /**
         根据对象创建 Task 请求
         
         url  方法内部会自动将 URL 包装成一个请求对象(默认是 GET 请求)
         completionHandler  完成之后的回调(成功或失败)
         
         param data     返回的数据(响应体)
         param response 响应头
         param error    错误信息
         */
        NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:
                                          ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                              
                                              //解析服务器返回的数据
    //                                          NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
                                              NSLog(@"%@", response);
    
                                              NSDictionary *dic = [self dictionaryWithdata:data];
                                              NSLog(@"%@", dic);
    
    
                                              //默认在子线程中解析数据
                                              NSLog(@"%@", [NSThread currentThread]);
                                          }];
        //发送请求(执行Task)
        [dataTask resume];
    }
    
    /*
            NSURLSessionDataTask 发送 POST 请求
            发送 POST 请求的步骤与发送 GET 请求一样:
                1.使用 NSURLSession 对象创建 Task
                2.执行 Task
     */
    - (void)setDataTask_post
    {
        //确定请求路径
        NSURL *url = [NSURL URLWithString:urlstr1];
        //创建可变请求对象
        NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
        //修改请求方法
        requestM.HTTPMethod = @"POST";
        //设置请求体
        requestM.HTTPBody = [@"username=name&password=000000&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
        //创建会话对象
        NSURLSession *session = [NSURLSession sharedSession];
        //创建请求 Task
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM completionHandler:
                                          ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                              
                                              //解析返回的数据
                                              NSDictionary *dic = [self dictionaryWithdata:data];
                                              NSLog(@"%@",dic);
    
                                          }];
        //发送请求
        [dataTask resume];
    }
    
    /*
                NSURLSessionDataTask 设置代理发送请求
                创建 NSURLSession 对象设置代理
                    1.使用 NSURLSession 对象创建 Task
                    2.执行 Task
     */
    
    - (void)setDataTask_delegate
    {
        //确定请求路径
        NSURL *url = [NSURL URLWithString:urlstr1];
        //创建可变请求对象
        NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
        //设置请求方法
        requestM.HTTPMethod = @"POST";
        //设置请求体
        requestM.HTTPBody = [@"username=name&password=000000&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    
        //创建会话对象,设置代理
        /**
         第一个参数:配置信息
         第二个参数:设置代理
         第三个参数:队列,如果该参数传递nil 那么默认在子线程中执行
         */
        self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                              delegate:self delegateQueue:nil];
        //创建请求 Task
        NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:requestM];
        //发送请求
        [dataTask resume];
        
    }
    //遵守协议,实现代理方法(常用的有三种代理方法)
    
    - (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler
    {
        //子线程中执行
        NSLog(@"接收到服务器响应的时候调用 -- %@", [NSThread currentThread]);
        
        self.dataM = [NSMutableData data];
        //默认情况下不接收数据
        //必须告诉系统是否接收服务器返回的数据
        completionHandler(NSURLSessionResponseAllow);
    }
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        
        NSLog(@"接受到服务器返回数据的时候调用,可能被调用多次");
        //拼接服务器返回的数据
        [self.dataM appendData:data];
    }
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        
        NSLog(@"请求完成或者是失败的时候调用");
        //解析服务器返回数据
        NSLog(@"%@", [self dictionaryWithdata:self.dataM]);
        
    }
    
    /*
        以下两个方法无法监听下载进度,如要获取下载进度,可以使用代理的方式进行下载。
     
        dataTask 和 downloadTask 下载对比:
     
        * NSURLSessionDataTask:
        1.下载文件可以实现离线断点下载,但是代码相对复杂.
     
        * NSURLSessionDownloadTask:
        1.下载文件可以实现断点下载,但不能离线断点下载
        2.内部已经完成了边接收数据边写入沙盒的操作
        3.解决了下载大文件时的内存飙升问题
    
    */
    
    /*
        NSURLSessionDataTask 简单下载:
        在前面请求数据的时候就相当于一个简单的下载过程,NSURLSessionDataTask 下载文件具体的步骤与上类似:
            1.使用 NSURLSession 对象创建一个 Task 请求
            2.执行请求
     */
    
    - (void)setDataTask_1
    {
        [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:
                                                        urlstrimage]
                                     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                         
                                         //解析数据
                                         UIImage *image = [UIImage imageWithData:data];
                                         NSLog(@"%@",image);
                                         //回到主线程设置图片
                                         dispatch_async(dispatch_get_main_queue(), ^{
    //                                         self.imageView.image = image;
                                         });
                                         
                                     }] resume];
    }
    
    
    
    
    /*
            NSURLSessionDownloadTask 简单下载:
            使用 NSURLSession 对象创建下载请求:
                1.在下载请求中移动文件到指定位置
                2.执行请求
    */
    
    - (void)setDownloadTask_2
    {
        //确定请求路径
        NSURL *url = [NSURL URLWithString:urlstrimage1];
        //创建请求对象
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        //创建会话对象
        NSURLSession *session = [NSURLSession sharedSession];
        //创建会话请求
        //优点:该方法内部已经完成了边接收数据边写沙盒的操作,解决了内存飙升的问题
        NSURLSessionDownloadTask *downTask = [session downloadTaskWithRequest:request
                                                            completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                                                
                                                                //默认存储到临时文件夹 tmp 中,需要剪切文件到 cache
                                                                NSLog(@"%@", location);//目标位置
                                                                NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]
                                                                                      stringByAppendingPathComponent:response.suggestedFilename];
                                                                
                                                                /**
                                                                 fileURLWithPath:有协议头
                                                                 URLWithString:无协议头
                                                                 */
                                                                [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
                                                                
                                                            }];
        //发送请求
        [downTask resume];
    }
    
    /*
     上传文件操作:
     使用 NSURLSession 进行上传文件操作,有些麻烦,如果嫌麻烦,也可以使用AFN框架就好。
     
     使用 NSURLSession 上传文件主要步骤及注意点
     主要步骤:
     1.确定上传请求的路径( NSURL )
     2.创建可变的请求对象( NSMutableURLRequest )
     3.修改请求方法为 POST
     4.设置请求头信息(告知服务器端这是一个文件上传请求)
     5.按照固定的格式拼接要上传的文件等参数
     6.根据请求对象创建会话对象( NSURLSession 对象)
     7.根据 session 对象来创建一个 uploadTask 上传请求任务
     8.执行该上传请求任务(调用 resume 方法)
     9.得到服务器返回的数据,解析数据(上传成功 | 上传失败)
     
     注意点:
     1.创建可变的请求对象,因为需要修改请求方法为 POST,设置请求头信息
     2.设置请求头这个步骤可能会被遗漏
     3.要处理上传参数的时候,一定要按照固定的格式来进行拼接
     4.需要采用合适的方法来获得上传文件的二进制数据类型( MIMEType,获取方式如下)
     对着该文件发送一个网络请求,接收到该请求响应的时候,可以通过响应头信息中的 MIMEType 属性得到
     使用通用的二进制数据类型表示任意的二进制数据 application/octet-stream
     调用 C 语言的 API 来获取
     [self mimeTypeForFileAtPath:@"此处为上传文件的路径"]
     */
    
    /*!
     * @brief 把格式化的JSON格式的字符串转换成字典
     * @return 返回字典
     */
    - (NSDictionary *)dictionaryWithdata:(NSData *)data {
        
        NSError *err;
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data
                                                            options:NSJSONReadingMutableContainers
                                                              error:&err];
        
        if(err) {
            NSLog(@"json解析失败:%@",err);
            return nil;
        }
        return dic;
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
    }
    @end
    

    3.AFNetworking
    AFNetworking是基于URL加载系统的网络框架,AFNetworking 1.0建立在NSURLConnection的基础API之上 ,AFNetworking 2.0开始使用NSURLConnection的基础API ,以及较新基于NSURLSession的API的选项。 AFNetworking 3.0现已完全基于NSURLSession的API,这降低了维护的负担,同时支持苹果增强关于NSURLSession提供的任何额外功能。
    我们重点探讨AFURLSessionManager和AFHTTPSessionManager两个类,因为它们都是基于NSURLSession的,前者的用法可以在官方文档上找到,而且用起来稍显麻烦,AFHTTPSessionManager的用法如下所示。

    下面的代码演示如何向服务器发送获取数据的GET请求

        // 创建HTTP会话管理器对象
        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        // AFNetworking默认接受的MIME类型是application/json
        // 有些服务器虽然返回JSON格式的数据但MIME类型设置的是text/html
        // 通过下面的代码可以指定支持的MIME类型有哪些
        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
            @"application/json", @"text/html", nil];
        // 向服务器发送GET请求获取JSON数据
        [manager
            // 统一资源定位符
            GET:@""
            // 请求参数
            parameters:@{  }
            // 当完成进度变化时回调的Block
            progress:nil
            // 服务器响应成功要回调的Block
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            }
            // 服务器响应失败要回调的Block
            failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            }
        ];
    

    下面的代码演示了如何向服务器发送上传数据的POST请求

        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        [manager
            // 统一资源定位符
            POST:@""
            // 请求参数
            parameters:@{ }
            // 构造请求报文消息体的Block
            constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
                // 可以调用appendPartWithFileData:name:fileName:mimeType:等方法
                // 将上传给服务器的数据放到请求报文的消息体中
            }
            // 当上传进度变化时回调的Block
            progress:^(NSProgress * _Nonnull uploadProgress) {
    
            }
            // 服务器响应成功要回调的Block
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {    
            } 
            // 服务器响应失败要回调的Block
            failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            }
        ];
    

    AFNetworking还封装了判断网络可达性的功能,使用该功能的代码如下所示:

        // 创建网络可达性管理器
        AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager manager];
        // 设置当网络状况发生变化时要回调的Block
        [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
            switch (status) {
                case AFNetworkReachabilityStatusNotReachable:
                    NSLog(@"没有网络连接");
                    break;
                case AFNetworkReachabilityStatusReachableViaWiFi:
                    NSLog(@"使用Wi-Fi");
                    break;
                case AFNetworkReachabilityStatusReachableViaWWAN:
                    NSLog(@"使用移动蜂窝网络");
                    break;
                default:
                    break;
            }
        }];
        // 开始监控网络状况变换
        [manager startMonitoring];
    

    补充
    1. 断点续传的实现原理
    HTTP协议设置请求头内容,支持只请求某个资源的某一部分。
    Range 请求的资源范围;
    Content-Range 响应的资源范围;
    在连接断开重连时,客户端只请求该资源未下载的部分,而不是重新请求整个资源,来实现断点续传。
    分块请求资源实例:
    Eg1:Range: bytes=306302- :请求这个资源从306302个字节到末尾的部分;
    Eg2:Content-Range: bytes 306302-604047/604048:响应中指示携带的是该资源的第306302-604047的字节,该资源共604048个字节;
    客户端通过并发的请求相同资源的不同片段,来实现对某个资源的并发分块下载。从而达到快速下载的目的。目前流行的FlashGet和迅雷基本都是这个原理。
    2. 多线程下载的原理
    下载工具开启多个发出HTTP请求的线程,每个http请求只请求资源文件的一部分,例如:Content-Range: bytes 20000-40000/47000;
    最后合并每个线程下载的文件。

    5. 数据解析

    通过HTTP从服务器获得的数据通常都是JSON格式或XML格式的,下面对这两种数据格式做一个简单的介绍。
    1.JSON
    JSON全称JavaScript对象表达式(JavaScript Object Notation),是目前最流行的存储和交换文本信息的语法,和XML相比,它更小、更快,更易解析,是一种轻量级的文本数据交换格式。

    JSON的语法规则可以简单的总结成以下几条:

    • 数据在名/值对中;
    1. 数据由逗号分隔;
    2. 花括号保存对象;
    3. 方括号保存数组。

    JSON中的值可以是:

    - 数字(整数或浮点数)
    - 字符串(在双引号中)
    - 逻辑值(true 或 false)
    - 数组(在方括号中)
    - 对象(在花括号中)
    - null
    

    不难看出,JSON用键值对的方式描述了对象,它的形态跟Objective-C的NSDictionary类型是完全一致的,可以通过NSJSONSerialization类的两个类方法实现JSON数据和字典或数组之间的相互转换。

    // 将数据转换成对象(通常是数组或字典)
    + (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
    // 将数组或字典装换成JSON数据
    + (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error
    

    通过服务器获得JSON数据后,最终需要将它转换成我们程序中的对象。事实上,将JSON转换成模型对象的操作在开发网络应用中是很常见的,我们可以使用KVC(Key-Value Coding)的方式将一个字典赋值给一个对象的属性。

    说明:KVC通常翻译为键值编码,它允许开发者通过名字访问对象属性,而无需调用明确的存取方法,这样就可以实现在运行时而不是在编译时确定属性的绑定。这种间接访问能让代码变得更灵活和更具复用性。

    对于对象中关联了其他对象或者对象的属性跟字典中的键不完全匹配的场景,KVC就显得不那么方便了,但是已经有很多优秀的第三方库帮助我们实现了JSON和模型对象的双向转换,比如JSONModel、MJExtension和YYModel。

    说明:YYModel> MJExtension> JSONModel(侵入性:模型类必须继承JSONModel)

    XML
    XML全称可扩展标记语言,被设计用来传输和存储数据。在JSON被广泛使用之前,XML是异构系统之间交换数据的事实标准,它是一种具有自我描述能力的传输数据的标记语言,如下所示。

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <note>
        <to>Tove</to>
        <from>Jani</from>
        <heading>Reminder</heading>
        <body>Don't forget me this weekend!</body>
    </note>
    

    因为不常用XML解析,所以其他的就不一一介绍了。

    6. 网络优化
    1. 数据格式优化,减少数据传输量和序列化时间
    2. 引入重试机制,提升网络服务成功率
    3. 弱网和网络抖动优化
      引入了网络质量参数,通过网络类型(网络类型切换时,例如WIFI和移动网络、4G/3G切换至2G时,)和端到端Ping值进行计算,根据不同的网络质量改变网络服务策略

    以上是iOS开发中跟网络相关的知识,但是还远不止这些,例如:如何通过证书保证网络通信的安全,如何有效的使用缓存来提升性能和减少网络开销以及URL缓存的过期模型和验证模型等。都需要我们深入学习研究!

    相关文章

      网友评论

          本文标题:iOS开发经验(13)-网络

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