[计算机网络]——TCP(二)

206 阅读10分钟

TCP如何实现可靠传输

重传机制

TCP实现可靠传输的方式之一,是提供序列号与确认应答。

在TCP中,当发送端的数据到达接收主机时,接收端主机会返回应该确认应答消息,表示已收到消息。

超时重传

重传机制的其中一个方式,就是在发送数据数据时,设定一个定时器,当超过指定的时间后,没有收到对方的ACK确认应答报文,就会重发该数据,也就是我们常说的超时重传

重传的原因有

  • 数据丢失
  • 应答ACK丢失

TCP的超时重传策略是超时时间间隔加倍,当遇到一次重传的时候,超时时间就会加倍,当收到ACK时,则会重置超时时间

快速重传

快速重传的工作方式是当收到三个相同的ACK报文时,会在定时器过期之前,重传丢失的报文段

SACK

快速重传只解决了一个问题,就是超时时间的问题,但是重传的时候,是重传之前的一个,还是重传之前的所有。

SACK叫选择性确定,这种方式需要在TCP头部的选项字段中加一个SACK,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

如图,发送方收到了三次同样的ACK确认报文,于是就会触发快速重传机制,通过SACK信息发现只有200-299这段数据丢失,于是只重发这一段数据。

产生时机

  • 当接收方收到乱序的数据时就会回复SACK,正常顺序的就会回复ACK

D-SACK

D-SACK是用来告诉发送方,有哪些数据被重复接收了。

例子:ACK丢失

  • 接收方发给发送方的两个ACK确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000-3499)
  • 于是接收方发现数据是重复收到的,回了一个SACK=3000-3500,告诉发送方3000-3500的数据已被接收
  • 这样发送方就知道数据没有丢,是接收方的ACK丢了

例子:网络延时

  • 数据包(1000-1499)被网络延迟了,导致发送方没有收到ACK1500的确认报文
  • 触发了快速重传机制后,被延迟的数据包(1000-1499)又到了接收方
  • 所以接收方回了一个SACK=1000-1500,表示这是个重复的包
  • 于是发送方就知道快速重传触发的原因不是发出去的包丢了,也不是回应的ACK丢了,而是因为网络延迟

所以,D-SACK的好处:

  1. 可以让发送方知道,是发出去的包丢了,还是接收方回应的ACK包丢了
  2. 可以知道是不是发送方的数据包被网络延迟了

滑动窗口

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区保留自己已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

流水线

发送方

  • 有一个窗口,TCP数据不是一个个发的,是每次发送多个

接收方

  • 有一个窗口,数据不是一个个接收的,也可以接收很多个,可以接收乱序的数据

序号和确认号机制

发送的数据是标上了序号的,而且发送方也是需要有接收方回复确认号来判断自己是否发送正常

累计确认

发送方不必一定要收到所有请求的ACK,收到某个ACK即可表明它前面的ACK都顺利到达发送方了

窗口

流量控制

发送方不能无脑地发数据给接收方,要考虑接收方处理能力。

如果一直无脑发数据给对方,但对方处理不过来,那么就会导致触发重发机制,对网络流量造成浪费。

所谓流量控制,就是TCP提供了一种机制可以让发送方根据接收方的实际接收能力,控制发送的数据量。

实现

利用TCP报文段中的首部字段——接收窗口来实现,这个接收窗口用于接收方发送给发送方,告诉发送方当前接收方还有多少可用的缓存空间

窗口收缩过程

TCP规定是不允许同时减少缓存同时收缩窗口的,或者先减少缓存再收缩窗口,

死锁

  1. 当接收方通知发送方窗口为0后,发送方就不会继续发送数据了,当接收方处理好数据后,窗口增大了,会通知发送方窗口变大的消息
  2. 会向发送方通告一个窗口非0的ACK报文,如果这个报文再网络中丢失了,这会导致发送方一直等待接收方的非0窗口通知,接收方也一直等待发送方的数据,如果不采取措施,会造成死锁的现象

解决:

  1. TCP为每个连接设有一个持续定时器,只要TCP连接一方收到对方的零窗口通知,就启动持续计时器
  2. 如果持续计时器超时,就会发送窗口探测报文,而对方在确认这个探测报文时,给出自己的接收窗口大小
    1. 如果窗口依旧为0,那么收到这个报文的一方就重新启动持续计时器
    2. 如果接收窗口不是0,那么死锁局面就打破
  1. 窗口探测次数一般为3次,如果3次后接收窗口还是0的话,有的TCP实现就会发RST报文来中断连接

糊涂窗口

如果接收方太忙了,来不及取走接收窗口里面的数据,那么就会导致发送方的发送窗口越来越小。到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合征。


要知道,TCP+IP头有40个字节,为了传输那几个字节要搭上这么大的开销,不经济。

所以糊涂窗口现象发生在发送方和接收方:

  • 接收方可以通告一个小的窗口
  • 发送方可以发送小的数据

于是要解决糊涂窗口综合征,要解决上面两个问题

  • 让接收方不通告小窗口给发送方
  • 让发送方避免发送小数据

接收方策略:

当窗口大小小于min(MSS,缓存大小的二分之一)时,就会向发送方通告窗口为0,也就阻止了发送方再发数据过来。等接收方处理了一些数据后,窗口大小>=MSS,或缓存空间一半时,就可以把窗口打开让发送方发送数据过来

发送方策略:

使用Nagle算法,算法思路是延时处理,它满足以下两个条件中的一条才可以发送数据

  • 等到窗口大小>=MSS或数据大小>=MSS
  • 之前所有包的ACK都已收到

拥塞控制

流量控制只是避免发送方的数据填满接收方的缓存,但是并不知道网络中到底发生了什么。

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等情况,这个时候TCP就会重传数据,一重传就会导致网络的负担更重。

于是TCP不能忽略网上发生的事情,于是就有了拥塞控制, 目的是避免发送方的数据填满整个网络

拥塞窗口

拥塞窗口cwnd是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化。

发送窗口swnd=min(cwnd,rwnd),也就是接收窗口和拥塞窗口的最小值

如何得知拥塞

其实只要发送方没有在规定时间内收到ACK应答报文,也就是发生了超时重传,就会认为网络中出现了拥塞。

慢启动

TCP在刚建立连接完成之后,首先有个慢启动过程。当发送方每收到一个ACK,拥塞窗口的大小就加一。

例子:

  • 连接建立完成之后,一开始初始化cwnd=1,表示可以传一个MSS大小的数据
  • 当收到一个ACK确认应答后,cwnd增加1,于是一次能够发送2个
  • 当收到两个ACK应答后,cwnd增加2,所以一次能发4个
  • 有一个慢启动门限ssthresh,当cwnd<ssthresh时,慢启动依旧在继续
  • cwnd>=ssthresh时,就会使用拥塞避免算法

拥塞避免算法

当拥塞窗口超过慢启动门限时就会进入拥塞避免算法。

那么进入拥塞避免算法后,它的规则是:每收到一个ACK,cwnd就增加1/cwnd

现假定ssthresh为8

  • 当8个ACK应答确认到来时,每个确认增加1/8,8个ACK确认cwnd一共增加1,变成了线性增长

像这样一直增长,网络就会进入拥塞状况了,于是就会出现丢包现象,这时就要对丢包的数据进行重传,当触发了重传机制,也就进入了拥塞发生算法


拥塞发送

当网络中出现拥塞时,重传有两种机制:

  • 超时重传
  • 快速重传

当发生超时重传时的拥塞发生算法

这个时候,sshresh和cwnd的值都会发生变化:

  • sshresh设置为cwnd/2
  • cwnd重置为1

接着就重新开始慢启动,慢启动是会突然减少数据流的,这种方式太激进了,反应也很强烈,会造成网络卡顿

当发生快速重传时的拥塞发生算法

  • cwnd=cwnd/2
  • ssthresh=cwnd
  • 进入快速恢复算法

快速恢复

快速重传和快速恢复一般同时使用,快速恢复算法认为,你还能收到三个重复的ACK说明网络也每那么糟糕。

然后快速恢复算法如下:

  • 拥塞窗口cwnd=ssthresh+3
  • 重传丢失的数据包
  • 如果再收到重复的ACK,则cwnd增加1
  • 如果收到新数据的ACK后,把cwnd设置为第一步中的ssthresh值,原因是该ACK确认了数据,说明恢复过程结束,即再次进入拥塞避免状态

TCP快速打开原理(TFO)

TFO流程

首轮的三次握手

首先客户端发送SYN给服务端,服务端收到。但现在服务端不是立刻恢复SYN+ACK,而是计算得到一个SYN Cookie,将这个Cookie放到TCP报文的Fast Open选项中,然后才给客户端返回。

客户端拿到这个Cookie值缓存下来,后面正常完成三次握手

后面的三次握手

后面的三次握手中,客户端会把之前缓存的CookieSYN发送给服务端,服务端验证了Cookie的合法性,如果不合法就直接丢弃,走正常的三次握手;如果合法就正常返回SYN+ACK,并且此时服务器能和客户端发送HTTP响应,也就是带上数据包

优势

TFO的优势在于后续的握手,在一个RTT时间就可以提前进行数据传输,积累起来还是一个比较大的优势