美文网首页
Alamofire - 4.8.2&三次握手&四次挥手

Alamofire - 4.8.2&三次握手&四次挥手

作者: Janek_m | 来源:发表于2019-08-18 12:11 被阅读0次

    学习-设计思路
    介绍 Alamofire属于纯网络层,提供了链式的request/response方法,JSON的传参和响应序列化,身份认证和其他特性。优雅之处在于它完完全全是由Swift写成的,并且没有从它的Objective-C版本-AFNetworking那继承任何特性。

    URLSessionConfiguration初始化模式
    • default 默认模式,通常我们用这种模式就足够了。default模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书
    • ephemeral系统没有任何持久性存储,所有内容的生命周期都与session相同,当session无效时,所有内容自动释放。(沙盒缓存器)
    • background创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的会话。background模式与default模式非常相似,不过background模式会用一个独立线程来进行数据传输。background模式可以在程序挂起,退出,崩溃的情况下运行task。也可以利用标识符来恢复进。注意,后台Session一定要在创建的时候赋予一个唯一的identifier,这样在APP下次运行的时候,能够根据identifier来进行相关的区分。如果用户关闭了APP,IOS 系统会关闭所有的background Session。而且,被用户强制关闭了以后,IOS系统不会主动唤醒APP,只有用户下次启动了APP,数据传输才会继续
            //初始化一个后台的模式的会话配置
            let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
            // 初始化session会话
            let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
            // 传入url开启下载
            session.downloadTask(with: url).resume()
    
            // 创建了一个session会话
            // dataTask -> url
            // resume
            // 以上是可以进行监听 ---
            // 数据回来 - 代理
    
    //MARK: - session代理
    
    extension ViewController:URLSessionDownloadDelegate{
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
            // 下载完成 - 开始沙盒迁移
            print("下载完成 - \(location)")
            let locationPath = location.path
            //拷贝到用户目录(文件名以时间戳命名)
            let documnets = NSHomeDirectory() + "/Documents/" + self.lgCurrentDataTurnString() + ".mp4"
            print("移动地址:\(documnets)")
            //创建文件管理器
            let fileManager = FileManager.default
            try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
    
        }
        
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
            print(" bytesWritten \(bytesWritten)\n totalBytesWritten \(totalBytesWritten)\n totalBytesExpectedToWrite \(totalBytesExpectedToWrite)")
            print("下载进度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
        }
        
        // 回调系统回调,告诉系统及时更新屏幕
        func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
            print("后台任务下载回来")
            DispatchQueue.main.async {
                guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
                backgroundHandle()
            }
        }
    }
    
    //  MARK:易错易忽略点
    // 开启后台下载权限
        func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
            self.backgroundSessionCompletionHandler = completionHandler
        }
    
    

    网络重点

    符号的概念:

    • 序列号seq 用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
    • 确认号ack 期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
    • 确认ACK 仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效。
    • 同步SYN: 连接建立时用于同步序号。当SYN=1ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
    • 终止FIN 用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接。
    • URG紧急指针是否有效。为1,表示某一位需要被优先处理
    • PSH提示接收端应用程序立即从TCP缓冲区把数据读走。
    • RST对方要求重新建立连接,复位。

    三次握⼿

    • 第一次握手: 客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号seq=x,此时,客户端进程进入了SYN-SENT状态,等待服务器的确认。
    • 第二次握手: 服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该ACK=1SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号seq=y,此时,服务器进程进入了SYN-RCVD状态,询问客户端是否做好准备。
    • 第三次握手: 客户端进程收到确认后,还要向服务器给出确认。确认报文的ACK=1ack=y+1,此时,连接建立,客户端进入ESTABLISHED状态,服务器端也进入ESTABLISHED状态。
    偷了个懒-自己没有去画图
    • 假如现在客户端想向服务端进行握手,它发送了第一个连接的请求报文,但是由于网络信号差或者服务器负载过多,这个请求没有立即到达服务端,而是在某个网络节点中长时间的滞留了,以至于滞留到客户端连接释放以后的某个时间点才到达服务端,那么这就是一个失效的报文,但是服务端接收到这个失效的请求报文后,就误认为客户端又发了一次连接请求,服务端就会想向客户端发出确认的报文,表示同意建立连接。
    • 假如不采用三次握手,那么只要服务端发出确认,表示新的建立就连接了。但是现在客户端并没有发出建立连接的请求,其实这个请求是失效的请求,一切都是服务端在自相情愿,因此客户端是不会理睬服务端的确认信息,也不会向服务端发送确认的请求,但是服务器却认为新的连接已经建立起来了,并一直等待客户端发来数据,这样的情况下,服务端的很多资源就没白白浪费掉了。
    • 采用三次握手的办法就是为了防止上述这种情况的发生,比如就在刚才的情况下,客户端不会向服务端发出确认的请求,服务端会因为收不到确认的报文,就知道客户端并没有要建立连接,那么服务端也就不会去建立连接,这就是三次握手的作用。

    四次挥手

    • 第一次挥手: 客户端进程发出连接释放FIN报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=x,此时,客户端进入FIN-WAIT-1(终止等待1)状态。
    • 第二次挥手: 服务端进程收到连接释放FIN报文,发出确认ACK报文,ACK=1ack=x+1,并且带上自己的序列号seq=y,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。此时,服务端通知高层的应用进程,客户端向服务端的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务端若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。客户端收到服务端的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,在这之前依然可以接收服务端发送过来的最后的数据。
    • 第三次挥手: 服务端将最后的数据发送给客户端完成后,就向客户端发送连接释放FIN报文,FIN=1ack=x+1,此时的序列号为seq=z,此时,服务端就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
    • 第四次挥手: 客户端接收到服务端的连接释放FIN报文后,必须发出确认报文,ACK=1ack=z+1,而自己的序列号是seq=x+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。此时服务端收到客户端发送过来的确认报文,就立即撤销自己的传输控制块TCB,进入CLOSED状态,注意此时的TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,客户端没有收到服务端发来的任何数据,证明服务端已正常关闭,此时客户端会撤销相应传输控制块TCB后,进入CLOSED状态。至此,TCP的连接才真正的断开了。(服务端结束TCP连接的时间要比客户端稍微早一些)
    偷了个懒-自己没有去画图
    • TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,在客户端想要断开连接时,客户端向服务端发送FIN报文,只是表示客户端已经没有数据要发送了,但是这个时候客户端还是可以接收来自服务端的数据。
    • 当服务端接收到FIN报文,并返回ACK报文,表示服务端已经知道了客户端要断开连接,客户端已经没有数据要发送了,但是这个时候服务端可能依然有数据要传输给客户端。
      当服务端的数据传输完之后,服务端会发送FIN报文给客户端,表示服务端也没有数据要传输了,服务端同意关闭连接,之后,客户端收到FIN报文,立即发送给客户端一个ACK报文,确定关闭连接。在之后,客户端和服务端彼此就愉快的断开了这次的TCP连接。
    • 或许会有疑问,为什么服务端的ACK报文和FIN报文都是分开发送的,但是在三次握手的时候却是ACK报文和SYN报文是一起发送的,因为在三次握手的过程中,当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是在关闭连接时,当服务端接收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,你发的FIN报文我收到了,只有等到服务端所有的数据都发送完了,才能发送FIN报文,因此ACK报文和FIN报文不能一起发送。所以断开连接的时候才需要四次挥手来完成。

    面试题(记录大神提供)

    • 【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
      答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
    • 【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
      答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
    • 【问题3】为什么不能用两次握手进行连接?
      答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
    • 【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
      TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

    OSI七层网络协议

    上三层【应用层、表示层、会话层】
    物理层【应用层、传输层、网络层、数据链路层】

    参考

    相关文章

      网友评论

          本文标题:Alamofire - 4.8.2&三次握手&四次挥手

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