AnthonyZero's Bolg

TCP重传机制/流量控制

超时重传

TCP要保证所有的数据包都可以到达,所以必需要有重传机制。

接收端给发送端的Ack确认只会确认最后一个连续的包,比如发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是会ack 3,然后收到了4,5(注意此时3因为可能丢包没收到),还是只会ack 3.

直观的解决方法是:接收方不做任何处理,当发送方发现收不到3的ack超时后,然后重传。缺点在于发送端不知道该重发3,还是重发3,4,5。这种就是被动等待的超时重传策略

永远记住 ACK 确认号是表示这之前的包都已经全部收到

快速重传

快速重传的含义是:当发送端收到 3 个或以上重复 ACK,就意识到之前发的包可能丢了,于是马上进行重传,不用傻傻的等到超时再重传。

比如前面接收端收到了1,2,于是会ack 3,此时数据3因为某些原因没有送到,4和5收到了,又ack 3两次。此时客户端收到三次ack 3,就重传数据3。服务端收到3之后,结合前面收到的数据直接ack 6完成数据传输

快速重传的利弊

  • 解决了被动等待timeout的问题
  • 还是无法解决是重传之前的一个还是重传所有数据包的问题

发送端收到三次重复 ack 自动触发快速重传

选择性重传(SACK)

基于快速重传,在tcp头里加了一个SACK的东西,SACK记录一个数值范围,表示哪些数据收到了(记录一下哪些包到了,哪些没到,针对性地重传。)

在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edge和right edge告知发送端已经收到了哪些区间的数据报.因此即使第3个包丢包了,当收到第 4、5个包之后,接收端依然会告诉发送端,这两个包到了。剩下第3个包没到,就重传这个包。这个过程也叫做选择性重传.它解决的是如何重传的问题。

如图所示:假设发送 5000 个字节的数据包,每次传输 1000 个字节,分5段传输,第二段因为某种原因不可达。
tcp4.jpg

流量控制

TCP 会把要发送的数据放入发送缓冲区(Send Buffer),接收到的数据放入接收缓冲区(Receive Buffer),应用程序会不停的读取接收缓冲区的内容进行处理。

流量控制做的事情就是,如果接收缓冲区已满,发送端应该停止发送数据。那发送端怎么知道接收端缓冲区是否已满呢?为了控制发送端的速率,接收端会告知客户端自己接收窗口(rwnd),也就是接收缓冲区中空闲的部分。

TCP 在收到数据包回复的 ACK 包里会带上自己接收窗口的大小,接收端需要根据这个值调整自己的发送策略。

通过抓包显示的 win = 29312 表示向对方声明自己的接收窗口的大小(还有多少缓冲区可以接收数据),对方收到以后,会把自己的「发送窗口」限制在29312大小之内。

TCP 滑动窗口分为两种: 发送窗口和接收窗口(发送和接受双方都各有一个)。发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收

发送方的滑动窗口(发送窗口)示意图:
tcp5.jpg

  • 发送窗口是发送端被允许发送的最大数据包大小,它表示了在某个时刻一端能拥有的最大未确认的数据包大小(最大在途数据),其大小等于图中 #2 区域和 #3 区域加起来的总大小

  • 可用窗口是发送端还能发送的最大数据包大小,它等于发送窗口的大小减去在途数据包大小,是发送端还能发送的最大数据包大小,对应于图中的 #3 号区域

  • 窗口的左边界表示成功发送并已经被接收方确认的最大字节序号,窗口的右边界是发送方当前可以发送的最大字节序号,滑动窗口的大小等于右边界减去左边界。

接收窗口示意图:
tcp6.jpg

REV 即 receive,NXT 表示下一个接收的位置,WND 表示接收窗口大小。

TCP使用滑动窗口进行流量控制,流量控制实际上是对发送方数据流量的控制。发送端根据这些信息动态调节窗口大小来控制发送,以达到流量控制的目的。

window full:

TCP Window Full 是站在发送端角度说的,此时发送的在途数据都还没有确认,可用窗口大小为0,在途字节数(发送窗口大小)等于对方接收窗口的情况,此时发送端不能再发任何数据给对方, Wireshark打上【TCP window Full】标记,表示我不能再发送数据了。直到发送的数据包得到 ACK。

zero window:

站在接收端角度来说,是接收端接收窗口满,告知发送方当前接收窗口已满(接收端回复的 ACK中携带了win=0),请停止发送,发送方此时停止发送数据。

Persist(坚持)定时器是专门为零窗口探测而准备的。当接收端 B 接收窗口为 0 时,发送端 A 此时不能再发送数据,那到时候恢复了可以继续传数据的时候发送端又怎么知晓呢?是因为发送端开启了坚持定时器,不断重试发送一个特殊的报文给接收端看对方窗口是否已经恢复,这个特殊的报文(零窗口探测包其实就是一个 ACK 包)只有一个字节。