上一篇文章一起了解了一下 TCP 基础知识:TCP 基础(三次握手/四次挥手)
- TCP 的特点
- TCP 协议描述
- TCP 三次握手
- TCP 四次挥手
这一篇文章主要是一起来深入了解学习一下关于 TCP 的其他的点。
1. 滑动窗口
A、顺序问题
B、丢包问题
C、流量控制
2. 拥塞窗口
TCP 可靠性的体现
首先我们知道 TCP 是一个可靠的连接,对应具体的现象就是所有的 TCP 包正常通信过程中都是有一问一答的过程,如果异常情况,一定时间内没有收到这个回复的包,那么发送端就会重新发送,直到有回复。我们前面又说过序列号和确认号的意思,在这里深入讨论的时候,序列号和确认号将是最重要的一点。
窗口
窗口是什么?
按照 TCP 可靠性来理解那么就会出现,所有的包都必须要一个一个来,收到一个才能进行下一个,这种方式确实可以解决基本的可靠性的问题(这种方式叫累计应答),但是无疑会让使得传输速率降低。所以就需要一个缓存区间,缓存已经接到的数据。例如:一次性发送端发送 10 条数据,由于各种原因,其中只是缺少了第 3 条数据,其他的数据都已经接收到了,最好的方式肯定是能够只要发送重新发送一次第 3 条数据就好了。那么缓存其他 9 个数据的缓冲区就是窗口,而且窗口是有大小的,还记得我们在 TCP 报文格式中说的吗?其中有一个字段就是窗口的大小。因为发送数据都是有回复的,所以在发送端也是有窗口的,所以窗口是在发送端和接受端都是有窗口的。
窗口在发送端区域:
- 发送了并且已经确认了的。
- 发送了尚未确认的。
- 没有发送,但是等待发送的。
- 没有发送,并且暂时不会发送的。
数据格式如下:
其中:
LastByteAcked: 表示第一部分和第二部分的分界线。
LastByteSent: 表示第二部分和第三部分的分界线。
WillByteSend: 表示第三部分和第四部分的分界线。
第二部分和第三部分就是组成了 TCP 协议中定义的窗口大小:Advertised window .也就是我们在 TCP 协议中关于窗口的有效位的数据。
其实理论上看起来第三部分和第四部分是不需要分开,但是为了流量控制,总不能一股脑的全部传给接收端。
窗口在接受端区域:
- 接受并且确认过的。
- 还没有接受但是马上就能接受的。
- 还没有接受而且是接受不了的部分。
数据格式如下:
其中:
MaxRcvBuffer:表示最大的缓存数据量。
LastByteRead :表示之前的数据都已经上报给应用层,从这个数据开始都是没有上报应用层的。
NextByteExpected - LastByteRead :表示已经确认接受了,但是上报给应用层的数据。
其中 TCP 中的窗口位数据大小( AdveriseWindow )就是: MaxRecBuffer -( NextByteExpected - LastByteRead -1).
接受端的第二部分和第一部分有一个明显区别,就是第一部分一定是连续的,而第二部分,可能由于其他原因,导致其中某些数据没有及时到达,存在缺失的情况。
顺序问题和丢包问题:
根据上述的两个数据结构图,我们模拟一种场景:
在发送端:
1.2.3 已经发送确认了;4~9 已经发送未确认;10 ~ 12 未发送,等待发送。13 ~ 15 未发送暂不发送。
在接受端:
1 ~ 5 已经接受并确认;
另外我们假设 6、7 还没有接受的,8 ~ 14 都已经接受到了,但是还没有确认。
两端综合来看:
其中 1.2.3 是接受端和发送端都已经确认好了的,没有什么异议。此时 4、5 的 ACK 还没有到达发送端。6、7 已经发送了,但是有接受到,可能是包丢失了,8 ~ 14 已经接收到了,因为 6、7 还没有确认,所以他们也不能确认。
根据上述场景我们来分析重传机制:
4、5 的 ACK 没有被发送端接收到,那么发送端会超时重传,就是对那些,发出去没有 ACK 的包重发( 没有收到回复就认为这个发出的数据,接收端可能没有接受到这个信息,也有可能接收端接收到了消息并且 ACK 也回了,发送端由于各种原因没有接收到或者没有及时接收到 ),什么时候开始重传呢?重传机制中这个时间( RTO )是必须大于往返时间 RTT ,否则引起没有必要的重传。也不能太长,因为等待 ACK 浪费时间,而影响访问速度。TCP 会网络 RTT 进行实时采样,这个值会根据网络状态变化而变化,RTO 也会随着者 RTT 变化而变化,我们称这种变化为自适用重传算法。
RTT 是什么?
RTT 是 Round Trip Time 的缩写,表示的含义为发送一个数据包,到接受到对应的 ACK,所花费的时间。主要由三部分组成 “链路的传播时间( 来回路程 ) + 接受端系统的处理时间 + 路由器缓存中的排队和处理时间”。其中路由器缓存排队和处理时间会会根据网络时间的变化的特别的明显。其余的两个相对会比较固定。
RTO是什么?
RTO 是 Retransmission TimeOut 即重传超时时间
基于上面的场景继续分析,如果此时的重发 4 的包,此时接收端收到 4 ,它发现自己已经有了 4 ,所以会把 4 的包抛弃掉,但是回复一个 4 、5的 ACK,因为 4、5 都已经接收到了,但接收端已经接收到了 4 、5的ACK,1、2、3、4 、5那么全部发送接收确认。TCP 会把接收的数据放在缓存区,当收到重发的时候,会对接收信息进行“累计通知”,表示 5 我也收到
继续:
因为 6、7 也已经发送了,但是接收端也没有接收到,超过其 RTO 之后,那么就会重发6、7 。此时接收端接收到了 7 ,但是没有接收到 6 ,此时会不会对 7 的包进行回复的,因为这个时候已经在数据产生了乱序,根据 TCP 的协议的规定,当接收端收到乱序片段时,需要重复发送 ACK 。
但是如果遇到一种场景的时间,需要能够快速处理呢?比如说 8、9、10包都已经接收到了,但是 7 已经没有接收到。那么这个时候有一种快速重传机制,即如果我接收到了连续三个沉余的包(dup ACK ),发送发就认为这个序号的包丢失,就可以立即重传,如:8、9、10已经接收到了,但是 7 还是没有接收到,那么此时又接收到了 9 那么,此时还是针对 6 的包回复,表示我希望下一个包希望接收的 7 那么此时,再连续发送三个沉余(dup ACK),接收端认为 7 丢失,进行快速重传。
还有一种方式就是加上一个 SACK,通过这种方式,可以一下子看出来到底是谁丢了。
SACK 是一个 TCP 的选项,来允许 TCP 单独发送确认非连续的片段,可以用于告知真正丢失的包,只播丢失的包,但是需要两端都支持这个才行。
流量控制问题
在 TCP 的数据格式中,是有一个字段表示窗口大小的。
在发送端和接收端都会有一个允许发送的窗口和允许接收的窗口。
前面有说过,窗口大小的定义。正常情况下,没确认以一个信息,窗口的起始序号,都会像左边移动。大小也会跟随着 TCP 数据格式包一起同步到两端。让对方都知彼此窗口大小。
不过流量控制主要是指控制发送端的速度,不让发送端发送的速率太快,接收端没有办法接收处理,然后数据溢出。流量控制由滑动窗口机制实现
发送端窗口(SWND):
如果发送端把所有的数据发送出去了,那么发送端的窗口大小为 0,我们知道窗口大小是由发送未确认和未发送可发送组成,但是此时把所有可发送的数据全部发送出去之后。所有的数据处于未确认的状态,那么窗口再也不能存储新的数据进来。所有的数据都是处于已发送等待确认的状态。
接收端窗口(RWND):
接收端窗口就是等待确认的数据序号组成,正常每确认一个窗口需要向右滑动和一个讯号,但是如果上次应用不处理已接收确认的数据,而MaxRecvBuffer 中的大小又是固定的,那么窗口的大小,一定会越来越来越小,最后直到为 0 ,让对方停止发送。
为了不出现死锁情况(发送端等待接收端告诉他,有足够窗口大小能够支持接收数据了,接收端以为发送端收到了自己让他发送信息的指令,两边就处于一种死锁状态),定义了机制接收端为 0 的时候,发送方会定时发送窗口探测数据包,看看是否能够调整窗口大小,但是如果对方处理比较慢的时候,又不是空出来一个窗口就去传满数据,处理方式为 当窗口太小的时候,不更新窗口,直到达到一定程度的大小的时候或者某一个定义好的数据,才会更新窗口
图中 1.2.3 包是被接收并确认的,所以为窗口对应的包的序号 +3。
拥塞控制问题
网络拥塞和流量控制的区别:
流量控制是为了控制窗口大小,担心处理不过来,防止数据溢出。
拥塞控制的问题是为了防止过多的数据注入网络中,避免出现网络负载过大。
慢开始算法
发送端有一个变量叫拥塞窗口(cwnd),拥塞窗口的大小取决于网络的拥塞情况,并且是动态变化的,和 RTO 有点类似。发送方一般让自己的发送窗口不大于拥塞窗口。
慢开始的算法的思路:
一条 TCP 连接开始,cwnd设置为一, 一开始发送的只发送一个,当收到一个确认之后,cwnd 就加 一,因此发送两个。每收到一个确认就加一,cwmd为四,就发送四个,确认四个,cwnd 就变成了八,就发送八个,就是成指数型增长。也就是从一个很小的值开始,不断开始指数增加
拥塞避免算法
有一个阈值为:ssthresh = 65535 个字节。当超过这个字节的时候,速度就不能成指数增长了,需要慢下来。此时每收到一个确认后,就变成了加 1/cwnd,全部收到回复就相当于加一。即在原来的基础上加一。如果之前是 10,那么就变成为 11.此时变成了线性变化了。但是不管是什么样的增长,都是已经在增长,那么就意味着这个数值只会越来越大。直到在某一个指时,出现了网络拥塞。拥塞就会可能造成丢包,需要超时重传。
快速恢复法
根据我们之前将的超时重传的方法,如果启动可快速重传,如果此时能够收到三个重复确认包时,那么 TCP 认为当前的情况不严重,因为大部分的包是没有丢的,执行“乘法减小”,此时会把 ssthresh 将为原来的一般,并且把 ssthresh 设置给 cwnd,后面每收到一个数据回复就 cwnd 就加一,呈线性增加。
整个过程如图所示:
这个图是用别人画的。和我没有半毛钱关系