tcpack---1简述

TCP重传机制

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

超时重传机制

一种是不回ack,死等3,当发送方发现收不到3的ack超时后,会重传3。一旦接收方收到3后,会ack 回 4——意味着3和4都收到了。

但是,这种方式会有比较严重的问题,那就是因为要死等3,所以会导致4和5即便已经收到了,而发送方也完全不知道发生了什么事,因为没有收到Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致4和5的重传。

对此有两种选择:

  • 一种是仅重传timeout的包。也就是第3份数据。
  • 另一种是重传timeout后所有的数据,也就是第3,4,5这三份数据。

这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等timeout,timeout可能会很长(在下篇会说TCP是怎么动态地计算出timeout的)

快速重传机制

于是,TCP引入了一种叫Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。

比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了,于是还是ack回2,后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了三个ack=2的确认,知道了2还没有到,于是就马上重转2。然后,接收端收到了2,此时因为3,4,5都收到了,于是ack回6

ast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题

SACK 方法

另外一种更好的方式叫:Selective Acknowledgment (SACK)(参看RFC 2018),这种方式需要在TCP头里加一个SACK的东西,ACK还是Fast Retransmit的ACK

在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有到。于是就优化了Fast Retransmit的算法。当然,这个协议需要两边都支持。

还需要注意一个问题——接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事会把问题复杂化了,但是,接收方这么做可能会有些极端情况,比如要把内存给别的更重要的东西。所以,发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远不能把SACK的包标记为Ack。

SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源

Duplicate SACK – 重复收到数据的问题

Duplicate SACK又称D-SACK,其主要使用了SACK来告诉发送方有哪些数据被重复接收了RFC-2883 里有详细描述和示例。下面举几个例子(来源于RFC-2883

D-SACK使用了SACK的第一个段来做标志,

  • 如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK
  • 如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SAC

引入了D-SACK,有这么几个好处:

1)可以让发送方知道,是发出去的包丢了,还是回来的ACK包丢了。

2)是不是自己的timeout太小了,导致重传。

3)网络上出现了先发的包后到的情况(又称reordering)

4)网络上是不是把我的数据包给复制了。

快速确认&延时确认

如果只是为了确认一个段就发送ack,这样会增加不必要额网络流量,为了解决这个问题最好方法就是延时确认,因为ack不占用tcp段资源,所以可以将ack于数据一起发送给对端。

tcp有两种模式来处理确认接收到的数据,即快速确认以及延时确认,在整个tcp 发送接收过程中,由于网络拥塞 收到小包等原因,会在快速确认以及延时确认之间切换。

当tcp进入延时确认,通常是接收到两个tcp 段产生一个确认或者延时在0.5s之类产生一个确认。

当前代码中存在检查是否在快速确认模式函数,但是太复杂了,

在延迟确认模式中,本端接收到数据包后,不会立即发送ACK给对端,而是等待一段时间,如果在此期间:

1. 本端有数据包要发送给对端。就在发送数据包的时候捎带上此ACK,如此一来就节省了一个报文。

2. 本端没有数据包要发送给对端。延迟确认定时器会超时,然后发送纯ACK给对端。

在具体实现中,用pingpong来区分这两种模式:

icsk->icsk_ack.pingpong == 0,表示使用快速确认。

icsk->icsk_ack.pingpong == 1,表示使用延迟确认。

快速确认模式是用于比较紧急的场景,此时需要立即通知对端,比如收到异常的数据报、接收窗口显著增大了。延迟确认模式则希望通过减少纯ACK的发送,来降低不必要的流量开销,所以此时要求数据的传输是双向的。在实际的传输过程中,会根据当时的场景来判断是使用快速确认模式还是延迟确认模式,因此ACK的发送模式并不是固定的,而是在这两种模式之间动态切换。


什么时候进行快速确认?

目前以下一种情况会导致快速确认

1、fin_wait1 以及fin_wait2状态下接收到fin

2、syn_sent状态下,paws校验失败或已接收到确认段,发送dack。

3、在接收慢路径下,paws校验失败或者已接收到确认段,发送dack

4、接收到多个全尺村的段且接收窗口的右端已经更新,也就是接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了

5、设置TCP_QUICKACK选项之后:进入快速确认模式,并立即发送一个ACK,这个选项并不是持久的,之后还是有可能进入延迟确认模式的

6、乱序tcp段队列中还有待处理的段

7、连续接收到两个小包,小于536B

8、接收到数据包的事件处理 (tcp_event_data_recv):数据包含有路由器的显式拥塞通知,进入快速确认模式

9、如果接收到的段有负荷,且其中一部分之前已经接收过了,则认为是Delayed ACK丢失,进入快速确认模式

转自参考:https://blog.csdn.net/zhangskd/article/details/45127565

Q:什么时候进行快速确认?
(1) 接收到数据包,检查是否需要发送ACK时 (__tcp_ack_snd_check):
1. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了。
    所以一般收到了两个数据包后,会发送ACK,而不是对每个数据包都进行确认。
2.  接收到数据包时,处于快速确认模式中。
3. 接收到数据包时,乱序队列不为空。

(2) 当接收队列中有数据复制到用户空间时,会判断是否要立即发送ACK (tcp_clean_rbuf):
 如果现在有ACK需要发送,满足以下条件之一,就可以立即发送:
1. icsk->icsk_ack.blocked为1,之前有Delayed ACK被用户进程阻塞了。
2. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed (所以经常是收到2个全尺寸段后发送ACK)
3. 本次复制到用户空间的数据量大于0,且满足以下条件之一:
    3.1 设置了ICSK_ACK_PUSHED2标志
    3.2 设置了ICSK_ACK_PUSHED标志,且处于快速确认模式中
如果原来没有ACK需要发送,但是现在的接收窗口显著增大了,也需要立即发送ACK通知对端。这里的显著增大是指:新的接收窗口大小不为0,且比原来接收窗口的剩余量增大了一倍。

什么时候进行延迟确认?

1. 快速确认模式中的ACK额度用完了,一般在快速确认了半个接收窗口的数据后,进入延迟确认模式。
2. 发送ACK时,因为内存分配失败,启动延迟确认定时器。
3. 接收到数据包,检查是否需要发送ACK时(__tcp_ack_snd_check),如果无法进行快速确认。
4. 使用TCP_QUICKACK选项禁用快速确认,设置的值为0。

 https://coolshell.cn/articles/11609.html

https://blog.csdn.net/zhangskd/article/details/45127565

原文地址:https://www.cnblogs.com/codestack/p/11920835.html