1.TCP可靠传输
可靠传输就是保证接收方收到的字节流和发送方发出的字节流是完全一样的 网络层是没有可靠传输机制的,尽自己最大的努力进行交付。而传输层使用 TCP 实现可靠传输,TCP 保证可靠传输的机制有如下几种:
- 校验和 Checksum(稍作了解即可)
- 序列号和确认应答机制(重要)
- 重传机制(重要)
- 流量控制(滑动窗口协议)(非常重要)
- 拥塞控制(重要)
1.1校验和
由发送端计算待发送 TCP 报文段的校验和,然后接收端对接收到的 TCP 报文段验证其校验和(TCP 的校验和是一个端到端的校验和)。 其目的是为了发现 TCP 的首部和数据在发送端到接收端之间是否发生了变动。如果接收方检测到校验和有差错,则该 TCP 报文段会被直接丢弃 TCP 在计算校验和时,需要加上一个 12 字节的伪首部,包含的信息有源 IP 地址、目的 IP 地址、保留字节 (置 0)、传输层协议号 (TCP 是 6)、TCP 报文长度 (首部 + 数据),伪首部是为了增加 TCP 校验和的检错能力:如根据目的 IP 地址检查这个 TCP 报文是不是传给我的、根据传输层协议号检查传输层协议是否选对了等等,伪首部只在校验的时候使用。
1.2序列号和确认应答机制
TCP 报文段的首部中有一个序号字段,指的是该报文段第一个字节的序号 确认应答机制:接收方收到 TCP 报文段后就会返回一个确认应答消息
1.3重传机制
传输报文时报文可能丢失,通常来说报文的丢失是暂时的,因此 TCP 能够发现和恢复报文丢失显得尤为重要
重传机制是 TCP 最基本的错误恢复功能,常见的重传机制有:超时重传,快速重传
1.3.1 超时重传
TCP 发送方在发送报文的时候,设定一个定时器,如果在规定的时间内没有收到接收方发来的 ACK 确认报文,发送方就会重传这个已发送的报文段。 对于发送方没有正确接收到接收方发来的 ACK 确认报文的情况,有以下两种(也就是在这两种情况下会发生超时重传): 1.报文段丢失 2.接收方的 ACK 确认报文丢失 超时重传时间我们一般用 RTO(Retransmission Timeout) 来表示 RTT(Round-Trip Time 往返时延):数据从网络一端传送到另一端所需的时间,也就是报文段的往返时间 超时重传时间 RTO 的值应该略大于报文往返 RTT 的值 当RTO>>RTT:网络的空闲时间增大,降低了网络传输效率 当RTO<<RTT:不必要的重传,导致网络负荷增大
如果超时重传的数据又超时了该怎么办?TCP 的策略是重传的超时间隔加倍。每进行一次超时重传,都会将下一次重传的超时时间间隔设为先前值的两倍。 超时触发重传存在的问题是,超时周期可能相对较长,为减少超时重传的等待时间呢,产生快速重传
1.3.2 快速重传
快速重传(Fast Retransmit)机制不以时间为驱动,而是以数据驱动重传 原理:每当接收方收到比期望序号大的失序报文段到达时,就向发送方发送一个冗余 ACK,指明下一个期待字节的序号。
- 接收方收到报文段 1,返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节)
- 接收方收到报文段 3,仍然返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节)
- 接收方收到报文段 4,仍然返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节)
- 接收方收到报文段 5,仍然返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节)
- 接收方收到 3 个对于报文段 1 的冗余 ACK,认为报文段 2 丢失,于是重传报文段 2
- 最后,接收方收到了报文段 2,此时因为报文段 3、4、5 都收到了,所以返回 6 的 ACK 确认报文(确认号为报文段 6 的第一个字节)
2.TCP流量控制
对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区, 将接收的数据放到接收缓存区。 流量控制:TCP头里有一个字段叫Window,用于接收方通知发送方自己还有多少缓冲区可以接收数据。发送方根据接收方的处理能力来发送数据,不会导致接收方处理不过来,是谓流量控制。
2.1TCP滑动窗口
滑动窗口的结构 窗口大小就是指无需等待确认应答,可以继续发送数据的最大值。 窗口的实现实际上是操作系统开辟的一个缓冲区,发送方在等待确认应答报文返回之前,必须在缓冲区中保留已发送的数据。如果在规定时间间隔内收到确认应答报文,就可以将数据从缓冲区中清除。
假设窗口大小为 3 个 TCP 段,那么发送方就可以连续发送 3 个 TCP 段,并且中途即使有 ACK 响应报文丢失,也可以通过下一个确认应答进行确认 例如:ACK 2 即使丢失了,也不会进行数据重发,可以通过下一个确认应答进行确认。只要发送方收到了 ACK 3 的确认应答,就意味着 3 之前的所有数据接收方都收到了。这个模式就叫累积确认或者累积应答。
2.1.1发送窗口
- 已发送并收到 ACK 确认应答的数据
- 已发送但未收到 ACK 确认应答的数据
- 未发送但总大小在接收方处理范围内的数据
- 未发送但总大小超过接收方处理范围的数据
- 当发送方把数据全部发送出去后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前无法继续发送数据。
- 如果27-30字节的ACK确认应答之后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 4个字节,因为有4个字节的数据被确认应答,那么后续也就可以发送40-44这 5 个字节的数据了
2.1.2接收窗口
- 已成功接收并确认的数据
- 未收到数据但可以接收的数据
- 未收到数据且不可以接收的数据(超出接收方窗口大小)
同样的,接收方的滑动窗口在成功接收并确认的数据后,窗口右移
2.2流量控制
主机 A 一直向主机 B 发送数据,不考虑主机 B 的接收能力,则可能导致主机 B 的接收缓冲区满了而无法再接收数据,从而导致大量的数据丢包,引发重传机制。 而在重传的过程中,若主机 B 的接收缓冲区情况仍未好转,则会将大量的时间浪费在重传数据上,降低传送数据的效率。 引入了流量控制机制:主机 B 通过告诉主机 A 自己接收缓冲区的大小,来使主机 A 控制发送的数据量。总结来说:所谓流量控制就是控制发送方发送速率,保证接收方来得及接收。 TCP 实现流量控制主要就是通过 滑动窗口协议 接收端会在发送 ACK 确认应答报文时,将自己的即时窗口大小(接收窗口 rwnd)填入,并跟随 ACK 报文一起发送出去。而发送方根据接收到的 ACK 报文中的窗口大小的值改变自己的发送速度。如果接收到窗口大小的值为 0,那么发送方将停止发送数据。并定期的向接收端发送窗口探测数据段,提醒接收端把窗口大小告诉发送端。
一个最简单的来回模拟过程:
- 首先双方三次握手,初始化各自的窗口大小,均为 200 个字节。
- 当前发送端给接收端发送 100 个字节,对于发送端而言,SND.NXT 要右移 100 个字节,当前的可用窗口减少 100 个字节
- 现在这 100 个到达了接收端,被放到接收端的缓冲队列中,不过此时由于大量负载的原因,接收端处理不了这么多字节,只能处理 40 个字节,剩下的 60 个字节被留在了缓冲队列中。
- 此时接收端的情况是处理能力不够用,发送端少发点,所以此时接收端的接收窗口应该缩小,具体来说,缩小 60 个字节,由 200 个字节变成了 140 字节,因为缓冲队列还有 60 个字节没被应用拿走。
- 接收端会在 ACK 的报文首部带上缩小后的滑动窗口 140 字节,发送端对应地调整发送窗口的大小为 140 个字节。
- 此时对于发送端而言,已经发送且确认的部分增加 40 字节,也就是 SND.UNA 右移 40 个字节,同时发送窗口缩小为 140 个字节。
3.TCP的拥塞控制
所谓拥塞就是说:在某段时间,对网络中某一资源的需求超过了该资源所能提供的可用部分(即 需大于供),网络的性能变差。 如果网络出现拥塞,TCP 报文可能会大量丢失,此时就会大量触发重传机制,从而导致网络拥塞程度更高,严重影响传输。其实只要发送方没有在规定时间内接收到 ACK 应答报文,也就是触发了重传机制,就会认为网络出现了拥塞。
因此当出现拥塞时,应当控制发送方的速率。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度,防止过多的数据注入到网络中。 为了调节发送方所要发送数据的量,定义了拥塞窗口 cwnd的概念。拥塞窗口是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化:
发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:
- 拥塞窗口(Congestion Window,cwnd)
- 慢启动阈值(Slow Start Threshold,ssthresh)
涉及到的算法有这几个:
拥塞窗口:指目前自己还能传输的数据量大小,是发送端的限制,限制发送窗口的大小。 发送窗口大小=min(接收窗口(rwnd),拥塞窗口(cwnd))
慢启动: 刚开始进入传输数据的时候,你是不知道现在的网路到底是稳定还是拥堵的,如果做的太激进,发包太急,那么疯狂丢包,造成雪崩式的网络灾难。好的方法是先探测一下,一点一点的提高发送数据包的数量,即由小到大逐渐增大拥塞窗口数值,cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍(指数增长)。 拥塞控制首先就是要采用一种保守的算法来慢慢地适应整个网路,这种算法叫慢启动
- 首先,三次握手,双方宣告自己的接收窗口大小
- 双方初始化自己的拥塞窗口(cwnd)大小
- 在开始传输的一段时间,发送端每收到一个 ACK(确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。),拥塞窗口大小加 1,也就是说,每经过一个 RTT,cwnd 翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,依次类推。
- 当 cwnd 到达慢启动阈值之后,好比踩了下刹车
这里会设置一个慢启动轮限 ssthresh 状态变量:
当 cwnd < ssthresh 时,继续使用慢启动算法 当 cwnd >= ssthresh 时,开始使用「拥塞避免算法」
拥塞避免: 原来每收到一个 ACK,cwnd 加1,现在到达阈值了,cwnd 只能加这么一点: 1 / cwnd。那你仔细算算,一轮 RTT 下来,收到 cwnd 个 ACK, 那最后拥塞窗口的大小 cwnd 总共才增加 1。 慢启动和拥塞避免是一起作用的,是一体的
无论是慢开始阶段还是拥塞避免,只要出现了网络拥塞(触发超时重传机制),慢开始轮限 sshresh 和 拥塞窗口大小 cwnd 的值会发生变化(乘法减小):
ssthresh 设为 cwnd/2 cwnd 重置为 1
快速重传: 在 TCP 传输的过程中,如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。 比如第 5 个包丢了,即使第 6、7 个包到达的接收端,接收端也一律返回第 4 个包的 ACK,当发送端收到 3 个重复的 ACK 时,意识到丢包了,于是马上进行重传,不用等到一个 RTO (超时重传,发送方在发送数据后,在多长时间内如果没有收到ACK,就重置重传计时器,并重传上次发送失败的报文)的时间到了才重传。
选择性重传 既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢? 在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edge和right edge告知发送端已经收到了哪些区间的数据报,因此,即使第 5 个包丢包了,当收到第 6、7 个包之后,接收端依然会告诉发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment),它解决的是如何重传的问题。
快速恢复: 当然,发送端收到三次重复 ACK 之后,发现丢包,觉得现在的网络已经有些拥塞了,自己会进入快速恢复阶段。 在这个阶段,发送端如下改变:
- 拥塞阈值降低为 cwnd 的一半
- cwnd 的大小变为拥塞阈值
- cwnd 线性增加
4.四种计时器
TCP共使用以下四种计时器:重传计时器、持续计时器、保活计时器和时间等待计时器。
重传计时器 当TCP发送报文段时,创建该特定报文段的重传计时器
- 若在计时器截止时间到之前收到了对此特定报文段的确认ACK,则撤销此计时器。
- 若在收到了对此特定报文段的确认之前计时器截止期到,则重传此报文段,并将计时器复位。
持续计时器 窗口大小通知:接收方可接收缓存的数据大小;接收方发送窗口大小通知给传送方,传送方才能发送数据,如果接收方缓存已满,会发送零窗口大小通知给发送方。
为了对付零窗口大小通知,TCP需要另一个计时器。 假定接收TCP宣布了窗口大小为零:发送TCP就停止传送报文段,直到接收TCP发送确认并宣布一个非零的窗口大小。但这个确认可能会丢失。我们知道在TCP中,对确认是不需要发送确认的。若确认丢失了,接收TCP并不知道,而是会认为它已经完成任务了,并等待着发送TCP接着会发送更多的报文段。但发送TCP由于没有收到确认,就等待对方发送确认来通知窗口的大小。双方的TCP都在永远地等待着对方。
为了解决这种死锁等待情况,TCP为每一个连接使用一个坚持计时器。当发送TCP收到一个窗口大小为零的确认时,就启动坚持计时器。当坚持计时器期限到时,发送TCP就发送探测报文段。这个报文段只有一个字节的数据,有一个序号,但它的序号永远不需要确认;甚至在计算对其他部分的数据的确认时该序号也被忽略。探测报文段提醒对方:确认已丢失,必须重传。
坚持计时器的值设置为重传时间的数值。但是,若没有收到从接收端来的响应,则需发送另一个探测报文段,并将坚持计时器的值加倍和复位。发送端继续发送探测报文段,将坚持计时器设定的值加倍和复位,直到这个值增大到门限值(通常是60秒)为止。在这以后,发送端每隔60秒就发送一个探测报文段,直到窗口重新打开。
保活计时器 保活计时器使用在某些实现中,用来防止在两个TCP之间的连接出现长时期的空闲。 假定客户打开了到服务器的连接,传送了一些数据,然后就保持静默了。也许这个客户出故障了。在这种情况下,这个连接将永远地处理打开状态。 要解决这种问题,在大多数的实现中都是使服务器设置保活计时器。每当服务器收到客户的信息,就将计时器复位。超时通常设置为2小时。 若服务器过了2小时还没有收到客户的信息,它就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,还没有响应就假定客户出了故障,因而就终止该连接。
时间等待器 2MSL时间等待计时器是在连接终止期间使用,即:TCP四次挥手最后一次。 当TCP关闭一个连接时,它并不认为这个连接马上就真正地关闭了。 时间等待期间中连接还处于一种中间过渡状态。主要是为了让重复的FIN报文段到达目的站,将其丢弃。 这个计时器的值通常设置为一个报文段的寿命期待值的两倍。
|