一、TCP协议起步
1. 什么是TCP协议
TCP是面向连接的协议,这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互“握手”,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。它有以下几个特点:
2. 为什么需要TCP协议,它在哪一层工作?
IP 层是“不可靠”的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中数据的完整性。如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
1. TCP三次握手的流程
2. 为什么是三次握手
为什么TCP连接确立需要三次握手,而不是两次?还是四次?这是一个经常能被问到的问题。接下来就几个方面分析为什么需要三次握手的原因:
2.1 避免历史连接(主要原因)
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
上面文字出自 RFC 793,三次握手的主要原因是为了防止旧的重复连接初始化造成混乱。
网络环境是比较复杂的,它不一定能保证我们先发送的数据包就一定能再我们期望的时间内送达,它可能半路 Poor Gay 了。也有可能超时后再抵达服务端,那么这时的TCP就会产生以下的一种情况:
如上图所示,如果一个SYN报文再超时后没有得到响应,客户端可能再次发送一个新的SYN请求,而这时旧的SYN请求可能比新的SYN请求先达到服务器。如果此时没有第三次连接来确认此次连接是否是历史连接的话,那么双方可能会建立两个链接?造成数据混乱。而如果是三次连接的话,客户端就有机会再去确认或者中止掉错误的连接,防止历史连接初始化了连接。
2.2 同步双方初始序列号
TCP协议通信的可靠性,是靠着“序列号”所维持的,上面我们看到,每次通信后,都会回一个ACK报文,ACK中的确认应答号都是靠着之前报文的序列号 + 1实现的,这样做有几个好处:
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带初始序列号的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送初始序列号给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了“三次握手”。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
2.3 避免资源浪费
上面我们在介绍避免历史连接的时候,就已经提及到了如果没有三次握手,无法确认客户端收到了服务端发送的建立连接的 ACK 确认序号信号。
假如我们现在是两次握手,如果有网络阻塞等原因造成旧的连接SYN请求还没抵达服务端,就已经达到了超时时间,那么客户端就会再次发送请求SYN报文,之后如果两次请求都能达到服务端的话,由于服务端现在只有两次握手,无法确定当前的SYN就是客户端想要的连接,只能一收到 SYN 就返回一个 ACK 报文再建立起一个连接,这么做就会造成资源的浪费。看下图所示:
小结,不能使用两次握手和四次握手的原因:
两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
1. 如何提高数据传输的可靠性
从上面我们聊TCP连接的建立过程,我们可以知道,当客户端的数据达到接收主机的时候,服务端主机会返回一个已收到消息的通知。这个消息就是前面提到的应答消息(ACK)。这个消息机制具体的实现就是,每次当接收端收到对端发送过来的消息时,都会将对端消息中的序列号+1,作为自己消息发送的应答号。
TCP通过肯定的确认应答(ACK)实现可靠的数据传输。当发送端将数据发送之后等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。当在一定时间内没有收到确认应答,发送端就认为数据已经丢失,那么就会进行重新发送。由此,即使产生了丢包,仍然能够保证数据能够到达对端,实现可靠传输。这个过程如下所示:
当然还有另外一种情况,就是主机B已经收到了数据,丢失确认应答的消息在传输过程中丢失,那么此时主机A在一段时间内没有收到确认应答消息,也会认为主机B没有收到消息,从而再发送一次,如下图:
这种情况在传输并不鲜见,如果一直收到主机A发送的重复数据,对于主机B来说,它必须去放弃一些重复的包,这就需要我们上面所提到的序列号了。根据序列号判断这个数据包先前是否收到过,如果收到过就放弃,如果没有收到就保留。序列号的生成有其独特算法,这里就不做赘述了。
2. 超时重发如何确定
超时重传机制是用来确保TCP传输的可靠性的重要手段之一,我们在上面已经提及过多次:每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复 ACK 进行确认,表示某 seq 号数据已经收到。发送端在发送了某个seq包后,等待一段时间,如果没有收到对应的 ACK 回复,就认为该报文丢失,会重传这个数据包。中间等待的这段时间我们称为超时时间。那么如何来确定这个超时时间呢?
比较理想的方式就是定义一个固定值的最小时间,它能保证“确认应答一定能在这个时间内返回”。但是这样却有很大的弊端,因为在长距离的通信时(访问外网)时,延迟肯定就大,那么如果把值设置得太大,那么短距离的通信就很不友好了(隔了那么就才能知道这个包丢了)。如果值设置得小了,那么每次长距离访问都被判定为丢包???所以这样设置固定值是不可取的,需要根据网络的延迟,动态设置超时时间。
在这里先引入两个概念:
TCP经典算法RTT是:,重传时间为:
// 挖坑,以后再写,这里要写的话太长了。
此外,数据也不会被无限次、反复地发送。达到一定重发次数后,如果仍然没有任何确认应答返回,就会判断为网络或者对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
3. 利用窗口控制提高速度
通过上面我们知道,TCP通过确认机制来保证数据传输的可靠性,在比较早的时候使用的是的方式,也称为的方式,就是我们上面提及的方式。发送数据的一方在发送数据的时候会启动定时器,但是如果数据或者ACK丢失,那么定时器到期之后,收不到ACK就认为发送出现状况,要进行重传。这样就会降低了通信的效率,如下图所示,这种方式被称为 。
由此我们引入了滑动窗口的概念,笼统讲就是我每次发送的包都有一个序列号,接收端必须对每一个包进行确认,这样主机 A一次可以多发送几个包,而不必等待ACK后再发送,同时接收端也要告知发送端它能够收多少个包,这样发送端发起来也有个限制。当然还需要保证顺序性,不要乱序,对于乱序的状况,我们可以允许等待一定情况下的乱序,比如先缓存提前到的数据,然后去等待需要的数据,如果一定时间没来就DROP掉,来保证顺序性。
3.1 数据类别
在TCP协议中,滑动窗口的引入解决了上述问题,接下来我们进一步了解滑动窗口的相关概念,首先发送端数据分为以下几个类别:
对于接收端也是有一个接收窗口的,类似发送端,接收端的数据有3个分类,因为接收端并不需要等待ACK所以它没有类似的接收并确认了的分类,情况如下:
3.2 发送窗口与可用窗口
对于发送方来讲,窗口内的包括两部分,就是发送窗口(已经发送了,但是没有收到ACK),可用窗口,接收端允许发送但是没有发送的那部分称为可用窗口。
3.3 滑动窗口原理
TCP并不是每一个报文段都会回复ACK的,可能会对两个或者多个报文段回复一个ACK【累计ACK】。比如说发送端有1、2、3 共计3个报文段,先发送了2,3两个报文段,但是接收方还期望收到1报文段,这个时候2,3报文段就只能放在缓存中等待报文1的空洞被填上,如果报文1,一直不来,报文2,3也将被丢弃,如果报文1来了,那么会发送一个ACK对这3个报文进行一次确认。接下来举一个例子来说明滑动窗口的原理:
举一个例子来说明一下滑动窗口的原理:
就是不断重复着上述的过程,随着窗口不断滑动,将真个数据流发送到接收端,实际上接收端的 Window Size 通告也是会变化的,接收端根据这个值来确定何时及发送多少数据,从对数据流进行流控。原理图如下图所示:
3.4 滑动窗口动态调整
主要是根据接收端的接收情况,动态去调整Window Size,然后来控制发送端的数据流量
总结一点,就是接收端可以根据自己的状况通告窗口大小,从而控制发送端的接收,进行流量控制。
了解完TCP连接的建立以及传输过程中的一些知识点后,再来了解TCP连接的终止就比较容易了。TCP连接中的双方都可以主要断开连接,断开连接后的主机中的资源将被释放,接下来我们先来了解终止过程的大概,如下图:
1. TCP四次挥手的过程
2. 为什么需要四次挥手
再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了:
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。
3. 为什么需要 TIME_WAIT
3.1 防止旧连接的数据包
这个过程比较容易理解,我们看下图先:
如果没有TIME_WAIT这个等待时间的话,像上图中由于网络延迟的消息,就有可能在下次连接建立的时候重新发送到接收端。因此TCP设计出在关闭连接时还需要等待 2 MSL这个时间,来确定是否还有后续的数据报发送过来,之后再关闭连接。
3.2 保证连接的正确关闭
TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
也就是说,TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
假设 TIME-WAIT 没有等待时间或时间过短,断开连接会造成什么问题呢?
从上面我们可以了解到,如果没有TIME_WAIT这段等待时间,那么客户端发送的 ACK应答包可能在网络中丢失,此时由于 TIME_WAIT 过短或者没有,那么客户端会直接进入 CLOSE 状态,而服务端却一直在 LAST_ACK 状态下等待。那么下一次连接的建立可能就无法成功。
如果TIME_WAIT的时间足够长,那么此时上面这种情况就得以解决:
4. TIME_WAIT 为什么是 2 MSL
MSL 是 的缩写,译为报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
MSL 与 TTL 的区别:MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。
TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。
2 MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME_WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2 MSL 时间将重新计时。
在 Linux 系统里 2 MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ri-ji/67881.html